mirror of
https://github.com/GTNewHorizons/Applied-Energistics-2-Unofficial.git
synced 2026-10-26 14:25:20 +00:00
Implement Sync Handler (#1214)
Co-authored-by: Martin Robertz <dream-master@gmx.net>
This commit is contained in:
1 parent
0ac43ad8fd
commit
a2525ee44d
32 files changed
+2508
-121
No files matched your search
@@ -699,6 +699,14 @@ public abstract class AEBaseGui extends GuiContainer implements IGuiTooltipHandl
|
||||
aeRenderItem.parent = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateScreen() {
|
||||
super.updateScreen();
|
||||
if (this.inventorySlots instanceof AEBaseContainer container) {
|
||||
container.tickClientSync();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Slot getSlot(final int mouseX, final int mouseY) {
|
||||
final List<Slot> slots = this.getInventorySlots();
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
package appeng.client.gui.implementations;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -29,7 +28,6 @@ import appeng.api.config.Settings;
|
||||
import appeng.api.config.Upgrades;
|
||||
import appeng.api.config.YesNo;
|
||||
import appeng.api.parts.ILevelEmitter;
|
||||
import appeng.api.storage.StorageName;
|
||||
import appeng.api.storage.data.AEStackTypeRegistry;
|
||||
import appeng.api.storage.data.IAEStackType;
|
||||
import appeng.client.gui.slots.VirtualMEPhantomSlot;
|
||||
@@ -39,11 +37,7 @@ import appeng.client.gui.widgets.MEGuiTextField;
|
||||
import appeng.client.gui.widgets.TypeToggleButton;
|
||||
import appeng.container.implementations.ContainerLevelEmitter;
|
||||
import appeng.core.AEConfig;
|
||||
import appeng.core.AELog;
|
||||
import appeng.core.localization.GuiText;
|
||||
import appeng.core.sync.network.NetworkHandler;
|
||||
import appeng.core.sync.packets.PacketConfigButton;
|
||||
import appeng.core.sync.packets.PacketValueConfig;
|
||||
import appeng.util.calculators.ArithHelper;
|
||||
import appeng.util.calculators.Calculator;
|
||||
|
||||
@@ -81,16 +75,17 @@ public class GuiLevelEmitter extends GuiUpgradeable {
|
||||
this.amountTextField = new MEGuiTextField(90, 12);
|
||||
this.amountTextField.x = this.guiLeft + 39;
|
||||
this.amountTextField.y = this.guiTop + 44;
|
||||
this.amountTextField.setText(Long.toString(this.container.getEmitterValue()));
|
||||
this.amountTextField.setFocused(true);
|
||||
((ContainerLevelEmitter) this.inventorySlots).setTextField(this.amountTextField);
|
||||
this.container.setTextField(this.amountTextField);
|
||||
this.validateText();
|
||||
|
||||
this.config = new VirtualMEPhantomSlot(
|
||||
17,
|
||||
42,
|
||||
((ContainerLevelEmitter) inventorySlots).getLvlEmitter().getAEInventoryByName(StorageName.CONFIG),
|
||||
0,
|
||||
GuiLevelEmitter::acceptType);
|
||||
GuiLevelEmitter::acceptType,
|
||||
this.container::getConfigStack,
|
||||
this.container::setConfigStack);
|
||||
this.registerVirtualSlots(this.config);
|
||||
}
|
||||
|
||||
@@ -281,17 +276,13 @@ public class GuiLevelEmitter extends GuiUpgradeable {
|
||||
final boolean backwards = Mouse.isButtonDown(1);
|
||||
|
||||
if (btn == this.setButton && this.setButton.enabled) {
|
||||
try {
|
||||
final String amountString = Long.toString(this.getAmountLong());
|
||||
this.amountTextField.setText(amountString);
|
||||
NetworkHandler.instance.sendToServer(new PacketValueConfig("LevelEmitter.Value", amountString));
|
||||
} catch (final IOException e) {
|
||||
AELog.debug(e);
|
||||
}
|
||||
final String amountString = Long.toString(this.getAmountLong());
|
||||
this.amountTextField.setText(amountString);
|
||||
this.container.setLevel(Long.parseLong(amountString));
|
||||
} else if (btn == this.craftingMode) {
|
||||
NetworkHandler.instance.sendToServer(new PacketConfigButton(this.craftingMode.getSetting(), backwards));
|
||||
this.container.rotateCraftingMode(backwards);
|
||||
} else if (btn == this.levelMode) {
|
||||
NetworkHandler.instance.sendToServer(new PacketConfigButton(this.levelMode.getSetting(), backwards));
|
||||
this.container.rotateLevelMode(backwards);
|
||||
} else {
|
||||
final boolean isPlus = btn == this.plus1 || btn == this.plus10
|
||||
|| btn == this.plus100
|
||||
@@ -313,16 +304,9 @@ public class GuiLevelEmitter extends GuiUpgradeable {
|
||||
return;
|
||||
}
|
||||
if (type != null) {
|
||||
final boolean next = this.container.getTypeFilters().toggle(type);
|
||||
tbtn.setEnabled(next);
|
||||
|
||||
try {
|
||||
NetworkHandler.instance.sendToServer(new PacketValueConfig("LevelEmitter.TypeFilter", type.getId()));
|
||||
} catch (final IOException e) {
|
||||
AELog.debug(e);
|
||||
}
|
||||
this.container.toggleTypeFilter(type);
|
||||
tbtn.setEnabled(this.container.getTypeFilters().isEnabled(type));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,25 +35,78 @@ public class VirtualMEPhantomSlot extends VirtualMESlot {
|
||||
boolean test(VirtualMEPhantomSlot slot, IAEStackType<?> type, int mouseButton);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface StackGetter {
|
||||
|
||||
@Nullable
|
||||
IAEStack<?> get();
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface StackSetter {
|
||||
|
||||
void set(@Nullable IAEStack<?> stack);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private final IAEStackInventory inventory;
|
||||
private final TypeAcceptPredicate acceptType;
|
||||
@Nullable
|
||||
private final StackGetter stackGetter;
|
||||
@Nullable
|
||||
private final StackSetter stackSetter;
|
||||
|
||||
public VirtualMEPhantomSlot(int x, int y, IAEStackInventory inventory, int slotIndex,
|
||||
TypeAcceptPredicate acceptType) {
|
||||
this(x, y, inventory, slotIndex, acceptType, null, null);
|
||||
}
|
||||
|
||||
public VirtualMEPhantomSlot(int x, int y, TypeAcceptPredicate acceptType, StackGetter stackGetter,
|
||||
StackSetter stackSetter) {
|
||||
this(x, y, null, 0, acceptType, stackGetter, stackSetter);
|
||||
}
|
||||
|
||||
private VirtualMEPhantomSlot(int x, int y, @Nullable IAEStackInventory inventory, int slotIndex,
|
||||
TypeAcceptPredicate acceptType, @Nullable StackGetter stackGetter, @Nullable StackSetter stackSetter) {
|
||||
super(x, y, slotIndex);
|
||||
this.inventory = inventory;
|
||||
this.showAmount = false;
|
||||
this.acceptType = acceptType;
|
||||
this.stackGetter = stackGetter;
|
||||
this.stackSetter = stackSetter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IAEStack<?> getAEStack() {
|
||||
return this.inventory.getAEStackInSlot(this.getSlotIndex());
|
||||
if (this.stackGetter != null) {
|
||||
return this.stackGetter.get();
|
||||
}
|
||||
|
||||
return this.getInventory().getAEStackInSlot(this.getSlotIndex());
|
||||
}
|
||||
|
||||
protected void setAEStack(@Nullable final IAEStack<?> stack) {
|
||||
if (this.stackSetter != null) {
|
||||
this.stackSetter.set(stack);
|
||||
return;
|
||||
}
|
||||
|
||||
this.getInventory().putAEStackInSlot(this.getSlotIndex(), stack);
|
||||
|
||||
NetworkHandler.instance.sendToServer(new PacketVirtualSlot(this.getStorageName(), this.getSlotIndex(), stack));
|
||||
}
|
||||
|
||||
public StorageName getStorageName() {
|
||||
return this.inventory.getStorageName();
|
||||
return this.getInventory().getStorageName();
|
||||
}
|
||||
|
||||
private IAEStackInventory getInventory() {
|
||||
if (this.inventory == null) {
|
||||
throw new IllegalStateException("Inventory is not available for synced virtual slots");
|
||||
}
|
||||
|
||||
return this.inventory;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,10 +203,7 @@ public class VirtualMEPhantomSlot extends VirtualMESlot {
|
||||
}
|
||||
|
||||
// Set on the client to avoid lag on slow networks
|
||||
inventory.putAEStackInSlot(this.getSlotIndex(), currentStack);
|
||||
|
||||
NetworkHandler.instance
|
||||
.sendToServer(new PacketVirtualSlot(this.getStorageName(), this.getSlotIndex(), currentStack));
|
||||
this.setAEStack(currentStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -75,6 +75,9 @@ import appeng.container.slot.SlotInaccessible;
|
||||
import appeng.container.slot.SlotPatternTerm;
|
||||
import appeng.container.slot.SlotPlayerHotBar;
|
||||
import appeng.container.slot.SlotPlayerInv;
|
||||
import appeng.container.sync.SyncEndpoint;
|
||||
import appeng.container.sync.SyncManager;
|
||||
import appeng.container.sync.SyncRegistrar;
|
||||
import appeng.core.AEConfig;
|
||||
import appeng.core.AELog;
|
||||
import appeng.core.localization.PlayerMessages;
|
||||
@@ -131,6 +134,7 @@ public abstract class AEBaseContainer extends Container {
|
||||
* GuiSync.Recurse requires lazy initialization
|
||||
*/
|
||||
private DataSynchronization dataSync;
|
||||
private final SyncManager syncManager = new SyncManager(this);
|
||||
|
||||
@Deprecated
|
||||
public AEBaseContainer(final InventoryPlayer ip, final TileEntity myTile, final IPart myPart) {
|
||||
@@ -363,6 +367,29 @@ public abstract class AEBaseContainer extends Container {
|
||||
}
|
||||
}
|
||||
|
||||
public final SyncManager getSyncManager() {
|
||||
return this.syncManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root registrar for the container synchronization system.
|
||||
* <p>
|
||||
* Call this from container constructors to register explicit synchronization handlers with stable, unique keys.
|
||||
* Handlers must be registered before synchronization starts. See {@link appeng.container.sync.SyncRegistrar} for
|
||||
* the available handler types.
|
||||
*/
|
||||
protected final SyncRegistrar syncRegistrar() {
|
||||
return this.syncManager.root();
|
||||
}
|
||||
|
||||
public final void receiveSyncData(final SyncEndpoint remoteEndpoint, final ByteBuf buf) {
|
||||
this.syncManager.readIncoming(remoteEndpoint, buf);
|
||||
}
|
||||
|
||||
public final void tickClientSync() {
|
||||
this.syncManager.tickClient();
|
||||
}
|
||||
|
||||
protected void bindPlayerInventory(final InventoryPlayer inventoryPlayer, final int offsetX, final int offsetY) {
|
||||
// bind player inventory
|
||||
for (int i = 0; i < 3; i++) {
|
||||
@@ -413,6 +440,8 @@ public abstract class AEBaseContainer extends Container {
|
||||
if (this.dataSync.hasChanges() && this.invPlayer.player instanceof EntityPlayerMP playerMP) {
|
||||
NetworkHandler.instance.sendTo(new PacketGuiDataSync(this.dataSync::writeChanges), playerMP);
|
||||
}
|
||||
|
||||
this.syncManager.tickServer();
|
||||
}
|
||||
|
||||
portableSourceTick();
|
||||
|
||||
@@ -11,6 +11,14 @@ import it.unimi.dsi.fastutil.Pair;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair;
|
||||
|
||||
/**
|
||||
* Legacy reflection-based synchronization for fields annotated with {@link GuiSync}.
|
||||
*
|
||||
* @deprecated Use {@link appeng.container.AEBaseContainer#syncRegistrar()} and
|
||||
* {@link appeng.container.sync.SyncRegistrar} to register sync handlers explicitly. See
|
||||
* {@link appeng.container.implementations.ContainerLevelEmitter} for an example.
|
||||
*/
|
||||
@Deprecated
|
||||
public class DataSynchronization {
|
||||
|
||||
private final Map<Integer, SynchronizedField<?>> fields = new HashMap<>();
|
||||
|
||||
@@ -17,7 +17,12 @@ import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotates that this field should be synchronized between the server and client.
|
||||
*
|
||||
* @deprecated Use {@link appeng.container.AEBaseContainer#syncRegistrar()} and
|
||||
* {@link appeng.container.sync.SyncRegistrar} to register sync handlers explicitly. See
|
||||
* {@link appeng.container.implementations.ContainerLevelEmitter} for an example.
|
||||
*/
|
||||
@Deprecated
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface GuiSync {
|
||||
@@ -26,7 +31,10 @@ public @interface GuiSync {
|
||||
|
||||
/**
|
||||
* Recurse into the class in search of more @GuiSync-ed values. The child IDs are offset by the value.
|
||||
*
|
||||
* @deprecated Use {@link appeng.container.sync.SyncRegistrar#child(String)} to create scoped handler keys.
|
||||
*/
|
||||
@Deprecated
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public static @interface Recurse {
|
||||
|
||||
@@ -6,7 +6,13 @@ import io.netty.buffer.ByteBuf;
|
||||
* Implement on classes to signal they can be synchronized to the client using {@link GuiSync}. <br/>
|
||||
* For this to work fully, the class also needs to have a public constructor that takes a {@link ByteBuf} argument and
|
||||
* be implemented equals to detect changes.
|
||||
*
|
||||
* @deprecated Use {@link appeng.container.sync.SyncCodec} and {@link appeng.container.sync.SyncCodecs}, and register
|
||||
* them via {@link appeng.container.sync.SyncRegistrar}. See
|
||||
* {@link appeng.container.sync.codecs.AEStackTypeFilterSyncCodec} and
|
||||
* {@link appeng.container.implementations.ContainerLevelEmitter} for examples.
|
||||
*/
|
||||
@Deprecated
|
||||
public interface IGuiPacketWritable {
|
||||
|
||||
void writeToPacket(ByteBuf buf);
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
|
||||
package appeng.container.implementations;
|
||||
|
||||
import static appeng.util.Platform.isServer;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.InventoryPlayer;
|
||||
import net.minecraft.inventory.IInventory;
|
||||
import net.minecraft.inventory.Slot;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import appeng.api.config.FuzzyMode;
|
||||
import appeng.api.config.LevelType;
|
||||
import appeng.api.config.RedstoneMode;
|
||||
@@ -27,49 +27,88 @@ import appeng.api.config.Settings;
|
||||
import appeng.api.config.YesNo;
|
||||
import appeng.api.parts.ILevelEmitter;
|
||||
import appeng.api.storage.StorageName;
|
||||
import appeng.api.storage.data.AEStackTypeRegistry;
|
||||
import appeng.api.storage.data.IAEStack;
|
||||
import appeng.api.storage.data.IAEStackType;
|
||||
import appeng.client.gui.IGuiSub;
|
||||
import appeng.client.gui.implementations.GuiLevelEmitter;
|
||||
import appeng.client.gui.widgets.MEGuiTextField;
|
||||
import appeng.container.PrimaryGui;
|
||||
import appeng.container.guisync.GuiSync;
|
||||
import appeng.container.interfaces.IContainerSubGui;
|
||||
import appeng.container.interfaces.IVirtualSlotHolder;
|
||||
import appeng.container.slot.SlotInaccessible;
|
||||
import appeng.container.slot.SlotRestrictedInput;
|
||||
import appeng.container.sync.SyncCodecs;
|
||||
import appeng.container.sync.SyncRegistrar;
|
||||
import appeng.container.sync.codecs.AEStackTypeFilterSyncCodec;
|
||||
import appeng.container.sync.deltas.TypeFilterDelta;
|
||||
import appeng.container.sync.handlers.ConfigEnumSyncHandler;
|
||||
import appeng.container.sync.handlers.DeltaObjectSyncHandler;
|
||||
import appeng.container.sync.handlers.LongSyncHandler;
|
||||
import appeng.container.sync.handlers.ObjectSyncHandler;
|
||||
import appeng.tile.inventory.AppEngInternalInventory;
|
||||
import appeng.util.AEStackTypeFilter;
|
||||
import appeng.util.Platform;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
|
||||
public class ContainerLevelEmitter extends ContainerUpgradeable implements IVirtualSlotHolder, IContainerSubGui {
|
||||
public class ContainerLevelEmitter extends ContainerUpgradeable implements IContainerSubGui {
|
||||
|
||||
private final ILevelEmitter lvlEmitter;
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
private MEGuiTextField textField;
|
||||
|
||||
@GuiSync(2)
|
||||
public LevelType lvType;
|
||||
|
||||
@GuiSync(3)
|
||||
public long EmitterValue = -1;
|
||||
|
||||
@GuiSync(4)
|
||||
public YesNo cmType;
|
||||
|
||||
@GuiSync(10)
|
||||
public AEStackTypeFilter typeFilters;
|
||||
private final IAEStack<?>[] configClientSlot = new IAEStack[1];
|
||||
private final LongSyncHandler emitterValueSync;
|
||||
private final DeltaObjectSyncHandler<@NotNull AEStackTypeFilter, TypeFilterDelta> typeFiltersSync;
|
||||
private final ConfigEnumSyncHandler<@NotNull LevelType> levelModeSync;
|
||||
private final ConfigEnumSyncHandler<@NotNull YesNo> craftingModeSync;
|
||||
private final ObjectSyncHandler<@Nullable IAEStack<?>> configStackSync;
|
||||
|
||||
public ContainerLevelEmitter(final InventoryPlayer ip, final ILevelEmitter te) {
|
||||
super(ip, te);
|
||||
this.lvlEmitter = te;
|
||||
this.typeFilters = new AEStackTypeFilter(this.lvlEmitter.getTypeFilters());
|
||||
|
||||
final SyncRegistrar sync = this.syncRegistrar();
|
||||
this.emitterValueSync = sync.longSync("emitterValue").onClientChange((oldValue, newValue) -> {
|
||||
if (this.textField != null) {
|
||||
this.textField.setText(String.valueOf(newValue));
|
||||
this.textField.setCursorPositionEnd();
|
||||
}
|
||||
}).onServerChange((oldValue, newValue) -> this.lvlEmitter.setReportingValue(newValue));
|
||||
|
||||
this.typeFiltersSync = sync
|
||||
.object("typeFilters", AEStackTypeFilterSyncCodec.INSTANCE, this.lvlEmitter.getTypeFilters())
|
||||
.onClientChange((oldValue, newValue) -> {
|
||||
if (Minecraft.getMinecraft().currentScreen instanceof GuiLevelEmitter guiLevelEmitter) {
|
||||
guiLevelEmitter.onUpdateTypeFilters();
|
||||
}
|
||||
}).onServerChange((oldValue, newValue) -> {
|
||||
if (newValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lvlEmitter.getTypeFilters().copyFrom(newValue);
|
||||
this.lvlEmitter.onChangeTypeFilters();
|
||||
});
|
||||
|
||||
this.levelModeSync = sync.configEnum(
|
||||
"levelMode",
|
||||
Settings.LEVEL_TYPE,
|
||||
LevelType.class,
|
||||
this.getUpgradeable().getConfigManager());
|
||||
this.craftingModeSync = sync.configEnum(
|
||||
"craftingMode",
|
||||
Settings.CRAFT_VIA_REDSTONE,
|
||||
YesNo.class,
|
||||
this.getUpgradeable().getConfigManager());
|
||||
this.configStackSync = sync
|
||||
.object(
|
||||
"configStack",
|
||||
SyncCodecs.aeStack(),
|
||||
this.lvlEmitter.getAEInventoryByName(StorageName.CONFIG).getAEStackInSlot(0))
|
||||
.onServerChange((oldValue, newValue) -> {
|
||||
final IAEStack<?> stack = newValue == null ? null : newValue.copy();
|
||||
this.lvlEmitter.getAEInventoryByName(StorageName.CONFIG).putAEStackInSlot(0, stack);
|
||||
});
|
||||
|
||||
// sub gui copy paste
|
||||
this.primaryGuiButtonIcon = new SlotInaccessible(new AppEngInternalInventory(null, 1), 0, 0, -9000);
|
||||
@@ -81,11 +120,6 @@ public class ContainerLevelEmitter extends ContainerUpgradeable implements IVirt
|
||||
this.textField = level;
|
||||
}
|
||||
|
||||
public void setLevel(final long l, final EntityPlayer player) {
|
||||
this.lvlEmitter.setReportingValue(l);
|
||||
this.EmitterValue = l;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupConfig() {
|
||||
final IInventory upgrades = this.getUpgradeable().getInventoryByName("upgrades");
|
||||
@@ -138,7 +172,6 @@ public class ContainerLevelEmitter extends ContainerUpgradeable implements IVirt
|
||||
|
||||
@Override
|
||||
public int availableUpgrades() {
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -147,94 +180,68 @@ public class ContainerLevelEmitter extends ContainerUpgradeable implements IVirt
|
||||
this.verifyPermissions(SecurityPermissions.BUILD, false);
|
||||
|
||||
if (Platform.isServer()) {
|
||||
this.EmitterValue = this.lvlEmitter.getReportingValue();
|
||||
this.setCraftingMode(
|
||||
(YesNo) this.getUpgradeable().getConfigManager().getSetting(Settings.CRAFT_VIA_REDSTONE));
|
||||
this.setLevelMode((LevelType) this.getUpgradeable().getConfigManager().getSetting(Settings.LEVEL_TYPE));
|
||||
this.emitterValueSync.set(this.lvlEmitter.getReportingValue());
|
||||
this.typeFiltersSync.set(this.lvlEmitter.getTypeFilters());
|
||||
this.craftingModeSync.syncFromConfig();
|
||||
this.levelModeSync.syncFromConfig();
|
||||
this.setFuzzyMode((FuzzyMode) this.getUpgradeable().getConfigManager().getSetting(Settings.FUZZY_MODE));
|
||||
this.setRedStoneMode(
|
||||
(RedstoneMode) this.getUpgradeable().getConfigManager().getSetting(Settings.REDSTONE_EMITTER));
|
||||
this.updateVirtualSlots(
|
||||
StorageName.CONFIG,
|
||||
this.lvlEmitter.getAEInventoryByName(StorageName.CONFIG),
|
||||
this.configClientSlot);
|
||||
this.configStackSync.set(this.lvlEmitter.getAEInventoryByName(StorageName.CONFIG).getAEStackInSlot(0));
|
||||
}
|
||||
|
||||
this.standardDetectAndSendChanges();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(final String field, final Object oldValue, final Object newValue) {
|
||||
if (field.equals("EmitterValue")) {
|
||||
if (this.textField != null) {
|
||||
this.textField.setText(String.valueOf(this.EmitterValue));
|
||||
public long getEmitterValue() {
|
||||
return this.emitterValueSync.get();
|
||||
}
|
||||
|
||||
if (String.valueOf(oldValue).equals("-1")) {
|
||||
this.textField.setCursorPositionEnd();
|
||||
}
|
||||
}
|
||||
} else if (field.equals("typeFilters")) {
|
||||
if (Minecraft.getMinecraft().currentScreen instanceof GuiLevelEmitter guiLevelEmitter) {
|
||||
guiLevelEmitter.onUpdateTypeFilters();
|
||||
}
|
||||
}
|
||||
public void setLevel(final long l) {
|
||||
this.emitterValueSync.set(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public YesNo getCraftingMode() {
|
||||
return this.cmType;
|
||||
return this.craftingModeSync.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCraftingMode(final YesNo cmType) {
|
||||
this.cmType = cmType;
|
||||
this.craftingModeSync.set(cmType);
|
||||
}
|
||||
|
||||
public void rotateCraftingMode(final boolean backwards) {
|
||||
this.craftingModeSync.rotate(backwards);
|
||||
}
|
||||
|
||||
public LevelType getLevelMode() {
|
||||
return this.lvType;
|
||||
return this.levelModeSync.get();
|
||||
}
|
||||
|
||||
private void setLevelMode(final LevelType lvType) {
|
||||
this.lvType = lvType;
|
||||
public void rotateLevelMode(final boolean backwards) {
|
||||
this.levelModeSync.rotate(backwards);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public AEStackTypeFilter getTypeFilters() {
|
||||
return this.typeFilters;
|
||||
return this.typeFiltersSync.get();
|
||||
}
|
||||
|
||||
public void toggleTypeFilter(@NotNull final IAEStackType<?> type) {
|
||||
this.typeFiltersSync.applyAndQueueDelta(TypeFilterDelta.toggle(type));
|
||||
}
|
||||
|
||||
public ILevelEmitter getLvlEmitter() {
|
||||
return this.lvlEmitter;
|
||||
}
|
||||
|
||||
public void toggleTypeFilter(String typeId, EntityPlayer player) {
|
||||
if (typeId == null || typeId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final IAEStackType<?> type = AEStackTypeRegistry.getType(typeId);
|
||||
if (type == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lvlEmitter.getTypeFilters().toggle(type);
|
||||
// Create a new instance so that GuiSync can detect changes
|
||||
this.typeFilters = new AEStackTypeFilter(this.lvlEmitter.getTypeFilters());
|
||||
this.lvlEmitter.onChangeTypeFilters();
|
||||
public @Nullable IAEStack<?> getConfigStack() {
|
||||
return this.configStackSync.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveSlotStacks(StorageName invName, Int2ObjectMap<IAEStack<?>> slotStacks) {
|
||||
for (var entry : slotStacks.int2ObjectEntrySet()) {
|
||||
this.lvlEmitter.getAEInventoryByName(StorageName.CONFIG)
|
||||
.putAEStackInSlot(entry.getIntKey(), entry.getValue());
|
||||
}
|
||||
|
||||
if (isServer()) {
|
||||
this.updateVirtualSlots(
|
||||
StorageName.CONFIG,
|
||||
this.lvlEmitter.getAEInventoryByName(StorageName.CONFIG),
|
||||
this.configClientSlot);
|
||||
}
|
||||
public void setConfigStack(@Nullable final IAEStack<?> stack) {
|
||||
this.configStackSync.set(stack);
|
||||
}
|
||||
|
||||
// for level terminal
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package appeng.container.sync;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public abstract class AbstractSyncHandler {
|
||||
|
||||
protected final SyncManager manager;
|
||||
protected final String key;
|
||||
protected final String fullKey;
|
||||
protected final SyncDirection direction;
|
||||
protected boolean dirty;
|
||||
private int id = -1;
|
||||
private boolean fullResyncRequested;
|
||||
private boolean remoteFullResyncRequested;
|
||||
|
||||
protected AbstractSyncHandler(final SyncManager manager, final String key, final String fullKey,
|
||||
final SyncDirection direction) {
|
||||
this.manager = Objects.requireNonNull(manager, "manager");
|
||||
this.key = Objects.requireNonNull(key, "key");
|
||||
this.fullKey = Objects.requireNonNull(fullKey, "fullKey");
|
||||
this.direction = Objects.requireNonNull(direction, "direction");
|
||||
}
|
||||
|
||||
public final String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
public final String getFullKey() {
|
||||
return this.fullKey;
|
||||
}
|
||||
|
||||
public final int getId() {
|
||||
if (this.id < 0) {
|
||||
throw new IllegalStateException("Sync handler ID has not been assigned yet: " + this.fullKey);
|
||||
}
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public final SyncDirection getDirection() {
|
||||
return this.direction;
|
||||
}
|
||||
|
||||
public final boolean canSendFrom(final SyncEndpoint endpoint) {
|
||||
return this.direction.canSendFrom(endpoint);
|
||||
}
|
||||
|
||||
public final boolean sendsInitialStateFrom(final SyncEndpoint endpoint) {
|
||||
return this.direction.sendsInitialStateFrom(endpoint);
|
||||
}
|
||||
|
||||
public final void readIncoming(final SyncEndpoint remoteEndpoint, final SyncMode mode, final ByteBuf buf)
|
||||
throws IOException {
|
||||
if (mode == SyncMode.REQUEST_FULL) {
|
||||
this.requestFullResync();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.direction.canReceiveFrom(remoteEndpoint)) {
|
||||
throw new IOException("Handler " + this.fullKey + " does not accept updates from " + remoteEndpoint + ".");
|
||||
}
|
||||
|
||||
this.readUpdate(remoteEndpoint, mode, buf);
|
||||
}
|
||||
|
||||
protected final void markDirtyInternal() {
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
protected final void requestFullResync() {
|
||||
this.fullResyncRequested = true;
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
protected final void requestRemoteFullResync() {
|
||||
this.remoteFullResyncRequested = true;
|
||||
}
|
||||
|
||||
protected final boolean shouldForceFullResync() {
|
||||
return this.fullResyncRequested;
|
||||
}
|
||||
|
||||
protected final void clearFullResyncRequest() {
|
||||
this.fullResyncRequested = false;
|
||||
}
|
||||
|
||||
final boolean shouldRequestRemoteFullResync() {
|
||||
return this.remoteFullResyncRequested;
|
||||
}
|
||||
|
||||
final void clearRemoteFullResyncRequest() {
|
||||
this.remoteFullResyncRequested = false;
|
||||
}
|
||||
|
||||
final void assignId(final int id) {
|
||||
if (id < 0) {
|
||||
throw new IllegalArgumentException("Handler ID must be non-negative.");
|
||||
}
|
||||
|
||||
if (this.id >= 0 && this.id != id) {
|
||||
throw new IllegalStateException(
|
||||
"Sync handler " + this.fullKey + " was already assigned a different ID: " + this.id);
|
||||
}
|
||||
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected abstract SyncMode writeUpdate(SyncEndpoint localEndpoint, boolean initialSync, ByteBuf buf)
|
||||
throws IOException;
|
||||
|
||||
protected abstract void readUpdate(SyncEndpoint remoteEndpoint, SyncMode mode, ByteBuf buf) throws IOException;
|
||||
|
||||
protected abstract String getTypeKey();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package appeng.container.sync;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Extends {@link SyncCodec} with an optional delta format for mutable values.
|
||||
* <p>
|
||||
* Delta updates are an optimization only. Callers must still be able to fall back to a full sync whenever a delta
|
||||
* cannot be produced or cannot be applied.
|
||||
*/
|
||||
public interface DeltaSyncCodec<T, D> extends SyncCodec<T> {
|
||||
|
||||
/**
|
||||
* Returns deltas from {@code previous} to {@code current}, or {@code null} or an empty list when this change should
|
||||
* be sent as a full update instead.
|
||||
*/
|
||||
@Nullable
|
||||
List<D> diff(T previous, T current);
|
||||
|
||||
void writeDelta(ByteBuf buf, D delta) throws IOException;
|
||||
|
||||
/**
|
||||
* Applies one delta payload to {@code base} and returns the updated value.
|
||||
*/
|
||||
T applyDelta(@Nullable T base, ByteBuf buf) throws IOException;
|
||||
|
||||
default T applyDeltaLocally(@Nullable final T base, final D delta) throws IOException {
|
||||
final ByteBuf buf = io.netty.buffer.Unpooled.buffer();
|
||||
this.writeDelta(buf, delta);
|
||||
return this.applyDelta(base, buf);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package appeng.container.sync;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Encodes and decodes values synchronized by {@link SyncRegistrar} object handlers.
|
||||
* <p>
|
||||
* Implementations define both the packet format and the value semantics used by the synchronization system to detect
|
||||
* changes. Use {@link SyncCodecs} for common codecs and factory methods.
|
||||
*/
|
||||
public interface SyncCodec<T> {
|
||||
|
||||
@FunctionalInterface
|
||||
interface Reader<T> {
|
||||
|
||||
T read(ByteBuf buf) throws IOException;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface Writer<T> {
|
||||
|
||||
void write(ByteBuf buf, T value) throws IOException;
|
||||
}
|
||||
|
||||
void write(ByteBuf buf, T value) throws IOException;
|
||||
|
||||
T read(ByteBuf buf) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns an isolated copy that can be retained as the last synchronized value.
|
||||
*/
|
||||
T copy(T value);
|
||||
|
||||
/**
|
||||
* Returns whether two values should be treated as equal for change detection.
|
||||
*/
|
||||
boolean valuesEqual(T a, T b);
|
||||
|
||||
/**
|
||||
* Returns the stable type identifier used when validating that both sides registered compatible handlers.
|
||||
*/
|
||||
default String getTypeKey() {
|
||||
return this.getClass().getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package appeng.container.sync;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import appeng.api.storage.data.IAEStack;
|
||||
import appeng.container.guisync.IGuiPacketWritable;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public final class SyncCodecs {
|
||||
|
||||
private static final SyncCodec<IAEStack<?>> AE_STACK_CODEC = of(
|
||||
IAEStack.class.getName(),
|
||||
IAEStack::writeToPacketGeneric,
|
||||
IAEStack::fromPacketGeneric,
|
||||
value -> value == null ? null : value.copy(),
|
||||
Objects::equals);
|
||||
|
||||
private SyncCodecs() {}
|
||||
|
||||
public static <T> SyncCodec<T> of(final String typeKey, final SyncCodec.Writer<T> writer,
|
||||
final SyncCodec.Reader<T> reader, final UnaryOperator<T> copier, final BiPredicate<T, T> equality) {
|
||||
Objects.requireNonNull(typeKey, "typeKey");
|
||||
Objects.requireNonNull(writer, "writer");
|
||||
Objects.requireNonNull(reader, "reader");
|
||||
Objects.requireNonNull(copier, "copier");
|
||||
Objects.requireNonNull(equality, "equality");
|
||||
|
||||
return new SyncCodec<>() {
|
||||
|
||||
@Override
|
||||
public void write(final ByteBuf buf, final T value) throws java.io.IOException {
|
||||
writer.write(buf, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T read(final ByteBuf buf) throws java.io.IOException {
|
||||
return reader.read(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T copy(final T value) {
|
||||
return copier.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean valuesEqual(final T a, final T b) {
|
||||
return equality.test(a, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeKey() {
|
||||
return typeKey;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T extends IGuiPacketWritable> SyncCodec<T> packetWritable(final Class<T> type,
|
||||
final SyncCodec.Reader<T> reader, final UnaryOperator<T> copier, final BiPredicate<T, T> equality) {
|
||||
Objects.requireNonNull(type, "type");
|
||||
return of(type.getName(), (buf, value) -> value.writeToPacket(buf), reader, copier, equality);
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>> SyncCodec<E> enumValue(final Class<E> enumClass) {
|
||||
Objects.requireNonNull(enumClass, "enumClass");
|
||||
|
||||
final E[] values = enumClass.getEnumConstants();
|
||||
return of(enumClass.getName(), (buf, value) -> buf.writeInt(value.ordinal()), buf -> {
|
||||
final int ordinal = buf.readInt();
|
||||
if (ordinal < 0 || ordinal >= values.length) {
|
||||
throw new java.io.IOException("Invalid enum ordinal " + ordinal + " for " + enumClass.getName());
|
||||
}
|
||||
|
||||
return values[ordinal];
|
||||
}, UnaryOperator.identity(), Objects::equals);
|
||||
}
|
||||
|
||||
public static SyncCodec<IAEStack<?>> aeStack() {
|
||||
return AE_STACK_CODEC;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package appeng.container.sync;
|
||||
|
||||
public enum SyncDirection {
|
||||
|
||||
SERVER_TO_CLIENT,
|
||||
CLIENT_TO_SERVER,
|
||||
BIDIRECTIONAL;
|
||||
|
||||
public boolean canSendFrom(final SyncEndpoint endpoint) {
|
||||
return switch (this) {
|
||||
case SERVER_TO_CLIENT -> endpoint == SyncEndpoint.SERVER;
|
||||
case CLIENT_TO_SERVER -> endpoint == SyncEndpoint.CLIENT;
|
||||
case BIDIRECTIONAL -> true;
|
||||
};
|
||||
}
|
||||
|
||||
public boolean canReceiveFrom(final SyncEndpoint remoteEndpoint) {
|
||||
return this.canSendFrom(remoteEndpoint);
|
||||
}
|
||||
|
||||
public boolean sendsInitialStateFrom(final SyncEndpoint endpoint) {
|
||||
return endpoint == SyncEndpoint.SERVER && this != CLIENT_TO_SERVER;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package appeng.container.sync;
|
||||
|
||||
/**
|
||||
* Identifies which side of the container sync channel a message is being sent from or received from.
|
||||
*/
|
||||
public enum SyncEndpoint {
|
||||
CLIENT,
|
||||
SERVER
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
package appeng.container.sync;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import appeng.api.config.Settings;
|
||||
import appeng.api.util.IConfigManager;
|
||||
import appeng.container.AEBaseContainer;
|
||||
import appeng.container.sync.handlers.AEStackInventorySyncHandler;
|
||||
import appeng.container.sync.handlers.BooleanSyncHandler;
|
||||
import appeng.container.sync.handlers.ConfigEnumSyncHandler;
|
||||
import appeng.container.sync.handlers.DeltaObjectSyncHandler;
|
||||
import appeng.container.sync.handlers.DoubleSyncHandler;
|
||||
import appeng.container.sync.handlers.IntSyncHandler;
|
||||
import appeng.container.sync.handlers.LongSyncHandler;
|
||||
import appeng.container.sync.handlers.ObjectSyncHandler;
|
||||
import appeng.core.AELog;
|
||||
import appeng.core.sync.network.NetworkHandler;
|
||||
import appeng.core.sync.packets.PacketContainerSync;
|
||||
import appeng.tile.inventory.IAEStackInventory;
|
||||
import appeng.util.Platform;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
|
||||
public final class SyncManager {
|
||||
|
||||
private final AEBaseContainer owner;
|
||||
private final List<AbstractSyncHandler> handlers = new ArrayList<>();
|
||||
private final Object2ObjectMap<String, AbstractSyncHandler> handlersByFullKey = new Object2ObjectOpenHashMap<>();
|
||||
private final Int2ObjectMap<AbstractSyncHandler> handlersById = new Int2ObjectOpenHashMap<>();
|
||||
private final SyncRegistrar rootRegistrar = new ScopedSyncRegistrar("");
|
||||
private boolean initialServerSyncSent;
|
||||
private boolean frozen;
|
||||
private int layoutHash;
|
||||
|
||||
public SyncManager(final AEBaseContainer owner) {
|
||||
this.owner = Objects.requireNonNull(owner, "owner");
|
||||
}
|
||||
|
||||
public SyncRegistrar root() {
|
||||
return this.rootRegistrar;
|
||||
}
|
||||
|
||||
public void freezeLayout() {
|
||||
if (this.frozen) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<AbstractSyncHandler> sortedHandlers = new ArrayList<>(this.handlers);
|
||||
sortedHandlers.sort(Comparator.comparing(AbstractSyncHandler::getFullKey));
|
||||
|
||||
this.handlersById.clear();
|
||||
|
||||
int nextId = 0;
|
||||
int hash = 1;
|
||||
for (final AbstractSyncHandler handler : sortedHandlers) {
|
||||
handler.assignId(nextId);
|
||||
this.handlersById.put(nextId, handler);
|
||||
nextId++;
|
||||
|
||||
hash = 31 * hash + handler.getFullKey().hashCode();
|
||||
hash = 31 * hash + handler.getDirection().ordinal();
|
||||
hash = 31 * hash + handler.getTypeKey().hashCode();
|
||||
}
|
||||
|
||||
this.layoutHash = hash != 0 ? hash : 1;
|
||||
this.frozen = true;
|
||||
}
|
||||
|
||||
public void tickServer() {
|
||||
if (!Platform.isServer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.freezeLayout();
|
||||
|
||||
if (!this.initialServerSyncSent) {
|
||||
this.sendUpdates(SyncEndpoint.SERVER, true);
|
||||
this.initialServerSyncSent = true;
|
||||
}
|
||||
|
||||
this.sendUpdates(SyncEndpoint.SERVER, false);
|
||||
}
|
||||
|
||||
public void tickClient() {
|
||||
if (!Platform.isClient()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.freezeLayout();
|
||||
this.sendUpdates(SyncEndpoint.CLIENT, false);
|
||||
}
|
||||
|
||||
public void readIncoming(final SyncEndpoint remoteEndpoint, final ByteBuf buf) {
|
||||
this.freezeLayout();
|
||||
|
||||
try {
|
||||
final int remoteLayoutHash = buf.readInt();
|
||||
final boolean initialSync = buf.readBoolean();
|
||||
|
||||
if (remoteLayoutHash != this.layoutHash) {
|
||||
AELog.warn(
|
||||
"Rejected container sync for %s due to layout mismatch. local=%d remote=%d side=%s initial=%s",
|
||||
this.owner.getClass().getName(),
|
||||
this.layoutHash,
|
||||
remoteLayoutHash,
|
||||
remoteEndpoint,
|
||||
initialSync);
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
final int handlerId = buf.readInt();
|
||||
if (handlerId == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
final AbstractSyncHandler handler = this.handlersById.get(handlerId);
|
||||
if (handler == null) {
|
||||
AELog.warn(
|
||||
"Rejected container sync for %s due to unknown handler id %d.",
|
||||
this.owner.getClass().getName(),
|
||||
handlerId);
|
||||
return;
|
||||
}
|
||||
|
||||
final SyncMode mode = SyncMode.fromOrdinal(buf.readByte());
|
||||
handler.readIncoming(remoteEndpoint, mode, buf);
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
AELog.warn(e, String.format("Failed to read container sync for %s.", this.owner.getClass().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
private void sendUpdates(final SyncEndpoint localEndpoint, final boolean initialSync) {
|
||||
if (this.handlers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ByteBuf data = Unpooled.buffer();
|
||||
data.writeInt(this.layoutHash);
|
||||
data.writeBoolean(initialSync);
|
||||
|
||||
int updates = 0;
|
||||
for (final AbstractSyncHandler handler : this.handlers) {
|
||||
if (handler.shouldRequestRemoteFullResync()) {
|
||||
updates++;
|
||||
data.writeInt(handler.getId());
|
||||
data.writeByte(SyncMode.REQUEST_FULL.ordinal());
|
||||
handler.clearRemoteFullResyncRequest();
|
||||
}
|
||||
|
||||
final ByteBuf payload = Unpooled.buffer();
|
||||
|
||||
try {
|
||||
final SyncMode mode = handler.writeUpdate(localEndpoint, initialSync, payload);
|
||||
if (mode == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
updates++;
|
||||
data.writeInt(handler.getId());
|
||||
data.writeByte(mode.ordinal());
|
||||
data.writeBytes(payload, payload.readerIndex(), payload.readableBytes());
|
||||
} catch (final IOException e) {
|
||||
AELog.warn(
|
||||
e,
|
||||
String.format(
|
||||
"Failed to serialize sync handler %s on %s.",
|
||||
handler.getFullKey(),
|
||||
localEndpoint));
|
||||
}
|
||||
}
|
||||
|
||||
if (updates == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.writeInt(-1);
|
||||
|
||||
final PacketContainerSync packet = new PacketContainerSync(this.owner.windowId, data);
|
||||
if (localEndpoint == SyncEndpoint.SERVER) {
|
||||
if (this.owner.getInventoryPlayer().player instanceof EntityPlayerMP playerMP) {
|
||||
NetworkHandler.instance.sendTo(packet, playerMP);
|
||||
}
|
||||
} else {
|
||||
NetworkHandler.instance.sendToServer(packet);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends AbstractSyncHandler> T register(final T handler) {
|
||||
if (this.frozen) {
|
||||
throw new IllegalStateException("Cannot register sync handler after synchronization has started.");
|
||||
}
|
||||
|
||||
final AbstractSyncHandler existing = this.handlersByFullKey.putIfAbsent(handler.getFullKey(), handler);
|
||||
if (existing != null) {
|
||||
throw new IllegalArgumentException("Duplicate sync fullKey: " + handler.getFullKey());
|
||||
}
|
||||
|
||||
this.handlers.add(handler);
|
||||
return handler;
|
||||
}
|
||||
|
||||
private static void validateKey(final String key) {
|
||||
if (key == null || key.isEmpty()) {
|
||||
throw new IllegalArgumentException("Sync key must not be empty.");
|
||||
}
|
||||
if (key.indexOf('.') >= 0) {
|
||||
throw new IllegalArgumentException("Sync key must not contain '.': " + key);
|
||||
}
|
||||
}
|
||||
|
||||
private final class ScopedSyncRegistrar implements SyncRegistrar {
|
||||
|
||||
private final String scope;
|
||||
|
||||
private ScopedSyncRegistrar(final String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull SyncRegistrar child(final @NotNull String key) {
|
||||
validateKey(key);
|
||||
return new ScopedSyncRegistrar(this.qualify(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull IntSyncHandler intS2C(final @NotNull String key) {
|
||||
return register(
|
||||
new IntSyncHandler(SyncManager.this, key, this.qualify(key), SyncDirection.SERVER_TO_CLIENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull IntSyncHandler intC2S(final @NotNull String key) {
|
||||
return register(
|
||||
new IntSyncHandler(SyncManager.this, key, this.qualify(key), SyncDirection.CLIENT_TO_SERVER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull IntSyncHandler intSync(final @NotNull String key) {
|
||||
return register(new IntSyncHandler(SyncManager.this, key, this.qualify(key), SyncDirection.BIDIRECTIONAL));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull LongSyncHandler longS2C(final @NotNull String key) {
|
||||
return register(
|
||||
new LongSyncHandler(SyncManager.this, key, this.qualify(key), SyncDirection.SERVER_TO_CLIENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull LongSyncHandler longC2S(final @NotNull String key) {
|
||||
return register(
|
||||
new LongSyncHandler(SyncManager.this, key, this.qualify(key), SyncDirection.CLIENT_TO_SERVER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull LongSyncHandler longSync(final @NotNull String key) {
|
||||
return register(new LongSyncHandler(SyncManager.this, key, this.qualify(key), SyncDirection.BIDIRECTIONAL));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BooleanSyncHandler booleanS2C(final @NotNull String key) {
|
||||
return register(
|
||||
new BooleanSyncHandler(SyncManager.this, key, this.qualify(key), SyncDirection.SERVER_TO_CLIENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BooleanSyncHandler booleanC2S(final @NotNull String key) {
|
||||
return register(
|
||||
new BooleanSyncHandler(SyncManager.this, key, this.qualify(key), SyncDirection.CLIENT_TO_SERVER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BooleanSyncHandler booleanSync(final @NotNull String key) {
|
||||
return register(
|
||||
new BooleanSyncHandler(SyncManager.this, key, this.qualify(key), SyncDirection.BIDIRECTIONAL));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DoubleSyncHandler doubleS2C(final @NotNull String key) {
|
||||
return register(
|
||||
new DoubleSyncHandler(SyncManager.this, key, this.qualify(key), SyncDirection.SERVER_TO_CLIENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DoubleSyncHandler doubleC2S(final @NotNull String key) {
|
||||
return register(
|
||||
new DoubleSyncHandler(SyncManager.this, key, this.qualify(key), SyncDirection.CLIENT_TO_SERVER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DoubleSyncHandler doubleSync(final @NotNull String key) {
|
||||
return register(
|
||||
new DoubleSyncHandler(SyncManager.this, key, this.qualify(key), SyncDirection.BIDIRECTIONAL));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @NotNull ObjectSyncHandler<T> objectS2C(final @NotNull String key, final @NotNull SyncCodec<T> codec,
|
||||
final @Nullable T initialValue) {
|
||||
return register(
|
||||
new ObjectSyncHandler<>(
|
||||
SyncManager.this,
|
||||
key,
|
||||
this.qualify(key),
|
||||
SyncDirection.SERVER_TO_CLIENT,
|
||||
codec,
|
||||
initialValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, D> @NotNull DeltaObjectSyncHandler<T, D> objectS2C(final @NotNull String key,
|
||||
final @NotNull DeltaSyncCodec<T, D> codec, final @Nullable T initialValue) {
|
||||
return register(
|
||||
new DeltaObjectSyncHandler<>(
|
||||
SyncManager.this,
|
||||
key,
|
||||
this.qualify(key),
|
||||
SyncDirection.SERVER_TO_CLIENT,
|
||||
codec,
|
||||
initialValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @NotNull ObjectSyncHandler<T> objectC2S(final @NotNull String key, final @NotNull SyncCodec<T> codec,
|
||||
final @Nullable T initialValue) {
|
||||
return register(
|
||||
new ObjectSyncHandler<>(
|
||||
SyncManager.this,
|
||||
key,
|
||||
this.qualify(key),
|
||||
SyncDirection.CLIENT_TO_SERVER,
|
||||
codec,
|
||||
initialValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, D> @NotNull DeltaObjectSyncHandler<T, D> objectC2S(final @NotNull String key,
|
||||
final @NotNull DeltaSyncCodec<T, D> codec, final @Nullable T initialValue) {
|
||||
return register(
|
||||
new DeltaObjectSyncHandler<>(
|
||||
SyncManager.this,
|
||||
key,
|
||||
this.qualify(key),
|
||||
SyncDirection.CLIENT_TO_SERVER,
|
||||
codec,
|
||||
initialValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @NotNull ObjectSyncHandler<T> object(final @NotNull String key, final @NotNull SyncCodec<T> codec,
|
||||
final @Nullable T initialValue) {
|
||||
return register(
|
||||
new ObjectSyncHandler<>(
|
||||
SyncManager.this,
|
||||
key,
|
||||
this.qualify(key),
|
||||
SyncDirection.BIDIRECTIONAL,
|
||||
codec,
|
||||
initialValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, D> @NotNull DeltaObjectSyncHandler<T, D> object(final @NotNull String key,
|
||||
final @NotNull DeltaSyncCodec<T, D> codec, final @Nullable T initialValue) {
|
||||
return register(
|
||||
new DeltaObjectSyncHandler<>(
|
||||
SyncManager.this,
|
||||
key,
|
||||
this.qualify(key),
|
||||
SyncDirection.BIDIRECTIONAL,
|
||||
codec,
|
||||
initialValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends Enum<E>> @NotNull ConfigEnumSyncHandler<E> configEnum(final @NotNull String key,
|
||||
final @NotNull Settings setting, final @NotNull Class<E> enumClass,
|
||||
final @NotNull IConfigManager configManager) {
|
||||
return register(
|
||||
new ConfigEnumSyncHandler<>(
|
||||
SyncManager.this,
|
||||
key,
|
||||
this.qualify(key),
|
||||
SyncDirection.BIDIRECTIONAL,
|
||||
setting,
|
||||
enumClass,
|
||||
configManager,
|
||||
enumClass.cast(configManager.getSetting(setting))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull AEStackInventorySyncHandler aeStackInventoryS2C(final @NotNull String key,
|
||||
final @NotNull IAEStackInventory inventory) {
|
||||
return register(
|
||||
new AEStackInventorySyncHandler(
|
||||
SyncManager.this,
|
||||
key,
|
||||
this.qualify(key),
|
||||
SyncDirection.SERVER_TO_CLIENT,
|
||||
inventory));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull AEStackInventorySyncHandler aeStackInventoryC2S(final @NotNull String key,
|
||||
final @NotNull IAEStackInventory inventory) {
|
||||
return register(
|
||||
new AEStackInventorySyncHandler(
|
||||
SyncManager.this,
|
||||
key,
|
||||
this.qualify(key),
|
||||
SyncDirection.CLIENT_TO_SERVER,
|
||||
inventory));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull AEStackInventorySyncHandler aeStackInventory(final @NotNull String key,
|
||||
final @NotNull IAEStackInventory inventory) {
|
||||
return register(
|
||||
new AEStackInventorySyncHandler(
|
||||
SyncManager.this,
|
||||
key,
|
||||
this.qualify(key),
|
||||
SyncDirection.BIDIRECTIONAL,
|
||||
inventory));
|
||||
}
|
||||
|
||||
private String qualify(final String key) {
|
||||
validateKey(key);
|
||||
return this.scope.isEmpty() ? key : this.scope + "." + key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package appeng.container.sync;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public enum SyncMode {
|
||||
|
||||
FULL,
|
||||
DELTA,
|
||||
REQUEST_FULL;
|
||||
|
||||
public static SyncMode fromOrdinal(final int ordinal) throws IOException {
|
||||
final SyncMode[] values = values();
|
||||
if (ordinal < 0 || ordinal >= values.length) {
|
||||
throw new IOException("Unknown sync mode ordinal: " + ordinal);
|
||||
}
|
||||
return values[ordinal];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package appeng.container.sync;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import appeng.api.config.Settings;
|
||||
import appeng.api.util.IConfigManager;
|
||||
import appeng.container.sync.handlers.AEStackInventorySyncHandler;
|
||||
import appeng.container.sync.handlers.BooleanSyncHandler;
|
||||
import appeng.container.sync.handlers.ConfigEnumSyncHandler;
|
||||
import appeng.container.sync.handlers.DeltaObjectSyncHandler;
|
||||
import appeng.container.sync.handlers.DoubleSyncHandler;
|
||||
import appeng.container.sync.handlers.IntSyncHandler;
|
||||
import appeng.container.sync.handlers.LongSyncHandler;
|
||||
import appeng.container.sync.handlers.ObjectSyncHandler;
|
||||
import appeng.tile.inventory.IAEStackInventory;
|
||||
|
||||
public interface SyncRegistrar {
|
||||
|
||||
/**
|
||||
* Creates a child registrar whose handlers are registered under the given key as an additional fullKey scope
|
||||
* segment.
|
||||
*/
|
||||
@NotNull
|
||||
SyncRegistrar child(@NotNull String key);
|
||||
|
||||
@NotNull
|
||||
IntSyncHandler intS2C(@NotNull String key);
|
||||
|
||||
@NotNull
|
||||
IntSyncHandler intC2S(@NotNull String key);
|
||||
|
||||
@NotNull
|
||||
IntSyncHandler intSync(@NotNull String key);
|
||||
|
||||
@NotNull
|
||||
LongSyncHandler longS2C(@NotNull String key);
|
||||
|
||||
@NotNull
|
||||
LongSyncHandler longC2S(@NotNull String key);
|
||||
|
||||
@NotNull
|
||||
LongSyncHandler longSync(@NotNull String key);
|
||||
|
||||
@NotNull
|
||||
BooleanSyncHandler booleanS2C(@NotNull String key);
|
||||
|
||||
@NotNull
|
||||
BooleanSyncHandler booleanC2S(@NotNull String key);
|
||||
|
||||
@NotNull
|
||||
BooleanSyncHandler booleanSync(@NotNull String key);
|
||||
|
||||
@NotNull
|
||||
DoubleSyncHandler doubleS2C(@NotNull String key);
|
||||
|
||||
@NotNull
|
||||
DoubleSyncHandler doubleC2S(@NotNull String key);
|
||||
|
||||
@NotNull
|
||||
DoubleSyncHandler doubleSync(@NotNull String key);
|
||||
|
||||
@NotNull
|
||||
<T, D> DeltaObjectSyncHandler<T, D> objectS2C(@NotNull String key, @NotNull DeltaSyncCodec<T, D> codec,
|
||||
T initialValue);
|
||||
|
||||
@NotNull
|
||||
<T> ObjectSyncHandler<T> objectS2C(@NotNull String key, @NotNull SyncCodec<T> codec, T initialValue);
|
||||
|
||||
@NotNull
|
||||
<T, D> DeltaObjectSyncHandler<T, D> objectC2S(@NotNull String key, @NotNull DeltaSyncCodec<T, D> codec,
|
||||
T initialValue);
|
||||
|
||||
@NotNull
|
||||
<T> ObjectSyncHandler<T> objectC2S(@NotNull String key, @NotNull SyncCodec<T> codec, T initialValue);
|
||||
|
||||
@NotNull
|
||||
<T, D> DeltaObjectSyncHandler<T, D> object(@NotNull String key, @NotNull DeltaSyncCodec<T, D> codec,
|
||||
T initialValue);
|
||||
|
||||
@NotNull
|
||||
<T> ObjectSyncHandler<T> object(@NotNull String key, @NotNull SyncCodec<T> codec, T initialValue);
|
||||
|
||||
@NotNull
|
||||
<E extends Enum<E>> ConfigEnumSyncHandler<E> configEnum(@NotNull String key, @NotNull Settings setting,
|
||||
@NotNull Class<E> enumClass, @NotNull IConfigManager configManager);
|
||||
|
||||
@NotNull
|
||||
AEStackInventorySyncHandler aeStackInventoryS2C(@NotNull String key, @NotNull IAEStackInventory inventory);
|
||||
|
||||
@NotNull
|
||||
AEStackInventorySyncHandler aeStackInventoryC2S(@NotNull String key, @NotNull IAEStackInventory inventory);
|
||||
|
||||
@NotNull
|
||||
AEStackInventorySyncHandler aeStackInventory(@NotNull String key, @NotNull IAEStackInventory inventory);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package appeng.container.sync.codecs;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import appeng.api.storage.data.AEStackTypeRegistry;
|
||||
import appeng.api.storage.data.IAEStackType;
|
||||
import appeng.container.sync.DeltaSyncCodec;
|
||||
import appeng.container.sync.deltas.TypeFilterDelta;
|
||||
import appeng.util.AEStackTypeFilter;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public final class AEStackTypeFilterSyncCodec implements DeltaSyncCodec<AEStackTypeFilter, TypeFilterDelta> {
|
||||
|
||||
public static final AEStackTypeFilterSyncCodec INSTANCE = new AEStackTypeFilterSyncCodec();
|
||||
|
||||
private static final byte OP_TOGGLE = 0;
|
||||
|
||||
private AEStackTypeFilterSyncCodec() {}
|
||||
|
||||
@Override
|
||||
public void write(final ByteBuf buf, final AEStackTypeFilter value) {
|
||||
value.writeToPacket(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AEStackTypeFilter read(final ByteBuf buf) {
|
||||
return new AEStackTypeFilter(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AEStackTypeFilter copy(final AEStackTypeFilter value) {
|
||||
return new AEStackTypeFilter(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean valuesEqual(final AEStackTypeFilter a, final AEStackTypeFilter b) {
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<TypeFilterDelta> diff(final AEStackTypeFilter previous, final AEStackTypeFilter current) {
|
||||
List<TypeFilterDelta> deltas = null;
|
||||
for (final IAEStackType<?> type : AEStackTypeRegistry.getAllTypes()) {
|
||||
if (previous.isEnabled(type) != current.isEnabled(type)) {
|
||||
if (deltas == null) {
|
||||
deltas = new ArrayList<>();
|
||||
}
|
||||
deltas.add(TypeFilterDelta.toggle(type));
|
||||
}
|
||||
}
|
||||
|
||||
return deltas;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDelta(final ByteBuf buf, final TypeFilterDelta delta) throws IOException {
|
||||
if (delta instanceof final TypeFilterDelta.Toggle toggle) {
|
||||
buf.writeByte(OP_TOGGLE);
|
||||
buf.writeByte(toggle.getNetworkId());
|
||||
return;
|
||||
}
|
||||
|
||||
throw new IOException("Unknown AEStackTypeFilter delta type: " + delta.getClass().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AEStackTypeFilter applyDelta(@Nullable final AEStackTypeFilter base, final ByteBuf buf) throws IOException {
|
||||
if (base == null) {
|
||||
throw new IOException("Cannot apply AEStackTypeFilter delta without a base value.");
|
||||
}
|
||||
|
||||
final byte op = buf.readByte();
|
||||
switch (op) {
|
||||
case OP_TOGGLE -> {
|
||||
final IAEStackType<?> type = this.requireType(buf.readByte());
|
||||
base.toggle(type);
|
||||
return base;
|
||||
}
|
||||
default -> throw new IOException("Unknown AEStackTypeFilter delta opcode: " + op);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeKey() {
|
||||
return AEStackTypeFilter.class.getName();
|
||||
}
|
||||
|
||||
private IAEStackType<?> requireType(final byte networkId) throws IOException {
|
||||
final IAEStackType<?> type = AEStackTypeRegistry.getTypeFromNetworkId(networkId);
|
||||
if (type == null) {
|
||||
throw new IOException("Unknown AE stack type network id: " + networkId);
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package appeng.container.sync.deltas;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import appeng.api.storage.data.IAEStack;
|
||||
|
||||
public final class AEStackInventoryDelta {
|
||||
|
||||
private final int slotIndex;
|
||||
private final @Nullable IAEStack<?> stack;
|
||||
|
||||
public AEStackInventoryDelta(final int slotIndex, @Nullable final IAEStack<?> stack) {
|
||||
this.slotIndex = slotIndex;
|
||||
this.stack = stack == null ? null : stack.copy();
|
||||
}
|
||||
|
||||
public int getSlotIndex() {
|
||||
return this.slotIndex;
|
||||
}
|
||||
|
||||
public @Nullable IAEStack<?> getStack() {
|
||||
return this.stack == null ? null : this.stack.copy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package appeng.container.sync.deltas;
|
||||
|
||||
import appeng.api.storage.data.AEStackTypeRegistry;
|
||||
import appeng.api.storage.data.IAEStackType;
|
||||
|
||||
public interface TypeFilterDelta {
|
||||
|
||||
static TypeFilterDelta toggle(final IAEStackType<?> type) {
|
||||
return new Toggle(AEStackTypeRegistry.getNetworkId(type));
|
||||
}
|
||||
|
||||
final class Toggle implements TypeFilterDelta {
|
||||
|
||||
private final byte networkId;
|
||||
|
||||
public Toggle(final byte networkId) {
|
||||
this.networkId = networkId;
|
||||
}
|
||||
|
||||
public byte getNetworkId() {
|
||||
return this.networkId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
package appeng.container.sync.handlers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import appeng.api.storage.data.IAEStack;
|
||||
import appeng.container.sync.AbstractSyncHandler;
|
||||
import appeng.container.sync.SyncDirection;
|
||||
import appeng.container.sync.SyncEndpoint;
|
||||
import appeng.container.sync.SyncManager;
|
||||
import appeng.container.sync.SyncMode;
|
||||
import appeng.container.sync.deltas.AEStackInventoryDelta;
|
||||
import appeng.tile.inventory.IAEStackInventory;
|
||||
import appeng.util.Platform;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
public class AEStackInventorySyncHandler extends AbstractSyncHandler {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface InventoryChangeListener {
|
||||
|
||||
void onChange(@NotNull IAEStackInventory inventory);
|
||||
}
|
||||
|
||||
private final @NotNull IAEStackInventory inventory;
|
||||
private final List<AEStackInventoryDelta> pendingDeltas = new ArrayList<>();
|
||||
private IAEStack<?>[] lastSentSnapshot;
|
||||
private @Nullable InventoryChangeListener clientChangeListener;
|
||||
private @Nullable InventoryChangeListener serverChangeListener;
|
||||
|
||||
public AEStackInventorySyncHandler(final SyncManager manager, final String key, final String fullKey,
|
||||
final SyncDirection direction, final @NotNull IAEStackInventory inventory) {
|
||||
super(manager, key, fullKey, direction);
|
||||
this.inventory = Objects.requireNonNull(inventory, "inventory");
|
||||
this.lastSentSnapshot = this.copyInventory();
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
public @NotNull IAEStackInventory get() {
|
||||
return this.inventory;
|
||||
}
|
||||
|
||||
public void markDirty() {
|
||||
this.clearQueuedDeltas();
|
||||
this.markDirtyInternal();
|
||||
}
|
||||
|
||||
public void applyAndQueueDelta(final AEStackInventoryDelta delta) {
|
||||
this.applyDelta(delta);
|
||||
this.pendingDeltas.add(delta);
|
||||
this.markDirtyInternal();
|
||||
}
|
||||
|
||||
public @NotNull AEStackInventorySyncHandler onClientChange(final InventoryChangeListener listener) {
|
||||
this.clientChangeListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NotNull AEStackInventorySyncHandler onServerChange(final InventoryChangeListener listener) {
|
||||
this.serverChangeListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SyncMode writeUpdate(final SyncEndpoint localEndpoint, final boolean initialSync, final ByteBuf buf)
|
||||
throws IOException {
|
||||
final boolean forceFullResync = this.shouldForceFullResync();
|
||||
if (!this.canSendFrom(localEndpoint)) {
|
||||
return null;
|
||||
}
|
||||
if (initialSync) {
|
||||
if (!this.sendsInitialStateFrom(localEndpoint)) {
|
||||
return null;
|
||||
}
|
||||
} else if (!this.dirty && !forceFullResync) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!initialSync && !forceFullResync && this.valuesEqual(this.lastSentSnapshot)) {
|
||||
this.clearQueuedDeltas();
|
||||
this.dirty = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
SyncMode mode = SyncMode.FULL;
|
||||
ByteBuf payload = buf;
|
||||
|
||||
if (!initialSync && !forceFullResync) {
|
||||
final ByteBuf fullPayload = Unpooled.buffer();
|
||||
this.writeFull(fullPayload);
|
||||
|
||||
final ByteBuf deltaPayload = this.writeDeltaPayload();
|
||||
if (deltaPayload != null && deltaPayload.readableBytes() < fullPayload.readableBytes()) {
|
||||
mode = SyncMode.DELTA;
|
||||
payload = deltaPayload;
|
||||
} else {
|
||||
payload = fullPayload;
|
||||
}
|
||||
} else {
|
||||
this.writeFull(payload);
|
||||
}
|
||||
|
||||
if (payload != buf) {
|
||||
buf.writeBytes(payload, payload.readerIndex(), payload.readableBytes());
|
||||
}
|
||||
|
||||
this.lastSentSnapshot = this.copyInventory();
|
||||
this.clearQueuedDeltas();
|
||||
this.dirty = false;
|
||||
this.clearFullResyncRequest();
|
||||
return mode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readUpdate(final SyncEndpoint remoteEndpoint, final SyncMode mode, final ByteBuf buf)
|
||||
throws IOException {
|
||||
if (mode == SyncMode.FULL) {
|
||||
this.readFull(buf);
|
||||
} else if (mode == SyncMode.DELTA) {
|
||||
try {
|
||||
this.applyDeltaPayload(buf);
|
||||
} catch (final IOException e) {
|
||||
if (this.getDirection() == SyncDirection.CLIENT_TO_SERVER || remoteEndpoint == SyncEndpoint.SERVER) {
|
||||
this.requestRemoteFullResync();
|
||||
} else {
|
||||
this.requestFullResync();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
throw new IOException(
|
||||
"AEStackInventorySyncHandler for " + this.fullKey + " does not support " + mode + ".");
|
||||
}
|
||||
|
||||
this.lastSentSnapshot = this.copyInventory();
|
||||
this.clearQueuedDeltas();
|
||||
this.dirty = false;
|
||||
|
||||
if (remoteEndpoint == SyncEndpoint.SERVER) {
|
||||
if (this.clientChangeListener != null) {
|
||||
this.clientChangeListener.onChange(this.inventory);
|
||||
}
|
||||
} else if (this.serverChangeListener != null) {
|
||||
this.serverChangeListener.onChange(this.inventory);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTypeKey() {
|
||||
return IAEStackInventory.class.getName();
|
||||
}
|
||||
|
||||
private void writeFull(final ByteBuf buf) throws IOException {
|
||||
for (int slot = 0; slot < this.inventory.getSizeInventory(); slot++) {
|
||||
IAEStack.writeToPacketGeneric(buf, this.inventory.getAEStackInSlot(slot));
|
||||
}
|
||||
}
|
||||
|
||||
private void readFull(final ByteBuf buf) throws IOException {
|
||||
for (int slot = 0; slot < this.inventory.getSizeInventory(); slot++) {
|
||||
this.inventory.putAEStackInSlot(slot, IAEStack.fromPacketGeneric(buf));
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable ByteBuf writeDeltaPayload() throws IOException {
|
||||
final List<AEStackInventoryDelta> deltas = this.pendingDeltas.isEmpty() ? this.diff() : this.pendingDeltas;
|
||||
if (deltas == null || deltas.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ByteBuf deltaPayload = Unpooled.buffer();
|
||||
deltaPayload.writeInt(deltas.size());
|
||||
for (final AEStackInventoryDelta delta : deltas) {
|
||||
deltaPayload.writeInt(delta.getSlotIndex());
|
||||
IAEStack.writeToPacketGeneric(deltaPayload, delta.getStack());
|
||||
}
|
||||
return deltaPayload;
|
||||
}
|
||||
|
||||
private @Nullable List<AEStackInventoryDelta> diff() {
|
||||
List<AEStackInventoryDelta> deltas = null;
|
||||
for (int slot = 0; slot < this.inventory.getSizeInventory(); slot++) {
|
||||
final IAEStack<?> previousStack = this.lastSentSnapshot[slot];
|
||||
final IAEStack<?> currentStack = this.inventory.getAEStackInSlot(slot);
|
||||
if (!Platform.isStacksIdentical(previousStack, currentStack)) {
|
||||
if (deltas == null) {
|
||||
deltas = new ArrayList<>();
|
||||
}
|
||||
deltas.add(new AEStackInventoryDelta(slot, currentStack));
|
||||
}
|
||||
}
|
||||
|
||||
return deltas;
|
||||
}
|
||||
|
||||
private void clearQueuedDeltas() {
|
||||
this.pendingDeltas.clear();
|
||||
}
|
||||
|
||||
private void applyDeltaPayload(final ByteBuf buf) throws IOException {
|
||||
final int deltaCount = buf.readInt();
|
||||
if (deltaCount <= 0) {
|
||||
throw new IOException("Invalid delta count " + deltaCount + " for " + this.fullKey + ".");
|
||||
}
|
||||
|
||||
for (int i = 0; i < deltaCount; i++) {
|
||||
final int slotIndex = buf.readInt();
|
||||
if (slotIndex < 0 || slotIndex >= this.inventory.getSizeInventory()) {
|
||||
throw new IOException("Invalid AE stack inventory slot index: " + slotIndex);
|
||||
}
|
||||
this.inventory.putAEStackInSlot(slotIndex, IAEStack.fromPacketGeneric(buf));
|
||||
}
|
||||
}
|
||||
|
||||
private void applyDelta(final AEStackInventoryDelta delta) {
|
||||
final int slotIndex = delta.getSlotIndex();
|
||||
if (slotIndex < 0 || slotIndex >= this.inventory.getSizeInventory()) {
|
||||
throw new IllegalArgumentException("Invalid AE stack inventory slot index: " + slotIndex);
|
||||
}
|
||||
|
||||
this.inventory.putAEStackInSlot(slotIndex, delta.getStack());
|
||||
}
|
||||
|
||||
private boolean valuesEqual(final IAEStack<?>[] snapshot) {
|
||||
for (int slot = 0; slot < this.inventory.getSizeInventory(); slot++) {
|
||||
if (!Platform.isStacksIdentical(this.inventory.getAEStackInSlot(slot), snapshot[slot])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private IAEStack<?>[] copyInventory() {
|
||||
final IAEStack<?>[] copy = new IAEStack[this.inventory.getSizeInventory()];
|
||||
for (int slot = 0; slot < copy.length; slot++) {
|
||||
final IAEStack<?> stack = this.inventory.getAEStackInSlot(slot);
|
||||
copy[slot] = stack == null ? null : stack.copy();
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package appeng.container.sync.handlers;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import appeng.container.sync.AbstractSyncHandler;
|
||||
import appeng.container.sync.SyncDirection;
|
||||
import appeng.container.sync.SyncEndpoint;
|
||||
import appeng.container.sync.SyncManager;
|
||||
import appeng.container.sync.SyncMode;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class BooleanSyncHandler extends AbstractSyncHandler {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface BooleanChangeListener {
|
||||
|
||||
void onChange(boolean oldValue, boolean newValue);
|
||||
}
|
||||
|
||||
private boolean value;
|
||||
private boolean lastSentValue;
|
||||
private boolean hasLastSentValue;
|
||||
private @Nullable BooleanChangeListener clientChangeListener;
|
||||
private @Nullable BooleanChangeListener serverChangeListener;
|
||||
|
||||
public BooleanSyncHandler(final SyncManager manager, final String key, final String fullKey,
|
||||
final SyncDirection direction) {
|
||||
super(manager, key, fullKey, direction);
|
||||
}
|
||||
|
||||
public boolean get() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeds the local value without marking it dirty or scheduling it to be sent.
|
||||
*/
|
||||
public void setLocalValue(final boolean value) {
|
||||
this.value = value;
|
||||
this.lastSentValue = value;
|
||||
this.hasLastSentValue = true;
|
||||
this.dirty = false;
|
||||
this.clearFullResyncRequest();
|
||||
}
|
||||
|
||||
public void set(final boolean value) {
|
||||
this.value = value;
|
||||
this.markDirty();
|
||||
}
|
||||
|
||||
public void markDirty() {
|
||||
this.markDirtyInternal();
|
||||
}
|
||||
|
||||
public BooleanSyncHandler onClientChange(final BooleanChangeListener listener) {
|
||||
this.clientChangeListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BooleanSyncHandler onServerChange(final BooleanChangeListener listener) {
|
||||
this.serverChangeListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SyncMode writeUpdate(final SyncEndpoint localEndpoint, final boolean initialSync, final ByteBuf buf) {
|
||||
final boolean forceFullResync = this.shouldForceFullResync();
|
||||
if (!this.canSendFrom(localEndpoint)) {
|
||||
return null;
|
||||
}
|
||||
if (initialSync) {
|
||||
if (!this.sendsInitialStateFrom(localEndpoint)) {
|
||||
return null;
|
||||
}
|
||||
} else if (!this.dirty && !forceFullResync) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!initialSync && !forceFullResync && this.hasLastSentValue && this.lastSentValue == this.value) {
|
||||
this.dirty = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
buf.writeBoolean(this.value);
|
||||
this.lastSentValue = this.value;
|
||||
this.hasLastSentValue = true;
|
||||
this.dirty = false;
|
||||
this.clearFullResyncRequest();
|
||||
return SyncMode.FULL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readUpdate(final SyncEndpoint remoteEndpoint, final SyncMode mode, final ByteBuf buf)
|
||||
throws IOException {
|
||||
if (mode != SyncMode.FULL) {
|
||||
throw new IOException("BooleanSyncHandler only supports full updates.");
|
||||
}
|
||||
|
||||
final boolean oldValue = this.value;
|
||||
final boolean newValue = buf.readBoolean();
|
||||
this.value = newValue;
|
||||
this.lastSentValue = newValue;
|
||||
this.hasLastSentValue = true;
|
||||
this.dirty = false;
|
||||
|
||||
if (remoteEndpoint == SyncEndpoint.SERVER) {
|
||||
if (this.clientChangeListener != null) {
|
||||
this.clientChangeListener.onChange(oldValue, newValue);
|
||||
}
|
||||
} else if (this.serverChangeListener != null) {
|
||||
this.serverChangeListener.onChange(oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTypeKey() {
|
||||
return "boolean";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package appeng.container.sync.handlers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import appeng.api.config.Settings;
|
||||
import appeng.api.util.IConfigManager;
|
||||
import appeng.container.sync.SyncCodec;
|
||||
import appeng.container.sync.SyncDirection;
|
||||
import appeng.container.sync.SyncEndpoint;
|
||||
import appeng.container.sync.SyncManager;
|
||||
import appeng.container.sync.SyncMode;
|
||||
import appeng.util.Platform;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public final class ConfigEnumSyncHandler<E extends Enum<E>> extends ObjectSyncHandler<E> {
|
||||
|
||||
private final Settings setting;
|
||||
private final IConfigManager configManager;
|
||||
private final Class<E> enumClass;
|
||||
|
||||
public ConfigEnumSyncHandler(final SyncManager manager, final String key, final String fullKey,
|
||||
final SyncDirection direction, final Settings setting, final Class<E> enumClass,
|
||||
final IConfigManager configManager, @Nullable final E initialValue) {
|
||||
super(manager, key, fullKey, direction, new ConfigEnumCodec<>(enumClass), initialValue);
|
||||
this.setting = Objects.requireNonNull(setting, "setting");
|
||||
this.configManager = Objects.requireNonNull(configManager, "configManager");
|
||||
this.enumClass = Objects.requireNonNull(enumClass, "enumClass");
|
||||
}
|
||||
|
||||
public void syncFromConfig() {
|
||||
this.set(this.readCurrentValue());
|
||||
}
|
||||
|
||||
public void rotate(final boolean backwards) {
|
||||
final E next = this.rotateValue(this.getCurrentOrConfigValue(), backwards);
|
||||
this.set(next);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readUpdate(final SyncEndpoint remoteEndpoint, final SyncMode mode, final ByteBuf buf)
|
||||
throws IOException {
|
||||
super.readUpdate(remoteEndpoint, mode, buf);
|
||||
|
||||
if (remoteEndpoint == SyncEndpoint.CLIENT) {
|
||||
final E value = this.get();
|
||||
if (value != null) {
|
||||
this.configManager.putSetting(this.setting, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private E readCurrentValue() {
|
||||
return this.enumClass.cast(this.configManager.getSetting(this.setting));
|
||||
}
|
||||
|
||||
private @Nullable E getCurrentOrConfigValue() {
|
||||
final E current = this.get();
|
||||
return current != null ? current : this.readCurrentValue();
|
||||
}
|
||||
|
||||
private E rotateValue(final @Nullable E currentValue, final boolean backwards) {
|
||||
if (currentValue == null) {
|
||||
throw new IllegalStateException("Cannot rotate a null config enum value.");
|
||||
}
|
||||
|
||||
return Platform.rotateEnum(currentValue, backwards, this.setting.getPossibleValues());
|
||||
}
|
||||
|
||||
private static final class ConfigEnumCodec<E extends Enum<E>> implements SyncCodec<E> {
|
||||
|
||||
private final E[] enumValues;
|
||||
|
||||
private ConfigEnumCodec(final Class<E> enumClass) {
|
||||
this.enumValues = Objects.requireNonNull(enumClass, "enumClass").getEnumConstants();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final ByteBuf buf, final E value) {
|
||||
buf.writeInt(value.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public E read(final ByteBuf buf) throws IOException {
|
||||
final int ordinal = buf.readInt();
|
||||
if (ordinal < 0 || ordinal >= this.enumValues.length) {
|
||||
throw new IOException("Invalid enum ordinal " + ordinal + " for config enum sync.");
|
||||
}
|
||||
|
||||
return this.enumValues[ordinal];
|
||||
}
|
||||
|
||||
@Override
|
||||
public E copy(final E value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean valuesEqual(final E a, final E b) {
|
||||
return a == b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeKey() {
|
||||
return this.enumValues.getClass().getComponentType().getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package appeng.container.sync.handlers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import appeng.container.sync.DeltaSyncCodec;
|
||||
import appeng.container.sync.SyncDirection;
|
||||
import appeng.container.sync.SyncManager;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
/**
|
||||
* Object sync handler that can batch and transmit one or more delta updates before falling back to a normal full sync.
|
||||
*/
|
||||
public class DeltaObjectSyncHandler<T, D> extends ObjectSyncHandler<T> {
|
||||
|
||||
private final DeltaSyncCodec<T, D> deltaCodec;
|
||||
private final List<D> pendingDeltas = new ArrayList<>();
|
||||
|
||||
public DeltaObjectSyncHandler(final SyncManager manager, final String key, final String fullKey,
|
||||
final SyncDirection direction, final DeltaSyncCodec<T, D> codec, final T initialValue) {
|
||||
super(manager, key, fullKey, direction, codec, initialValue);
|
||||
this.deltaCodec = codec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a known local delta immediately and queues it for transmission on the next flush. If delta sync later
|
||||
* fails, normal resync recovery still falls back to a full update/request as needed.
|
||||
*/
|
||||
public void applyAndQueueDelta(final D delta) {
|
||||
if (this.getCurrentValue() == null) {
|
||||
throw new IllegalStateException("Cannot apply a delta to a null sync value: " + this.fullKey);
|
||||
}
|
||||
|
||||
try {
|
||||
this.setCurrentValue(this.deltaCodec.applyDeltaLocally(this.copyValue(this.getCurrentValue()), delta));
|
||||
} catch (final IOException e) {
|
||||
throw new IllegalStateException("Failed to apply local delta for " + this.fullKey + ".", e);
|
||||
}
|
||||
|
||||
this.pendingDeltas.add(delta);
|
||||
this.markDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DeltaObjectSyncHandler<T, D> onClientChange(final ObjectChangeListener<T> listener) {
|
||||
super.onClientChange(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DeltaObjectSyncHandler<T, D> onServerChange(final ObjectChangeListener<T> listener) {
|
||||
super.onServerChange(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clearQueuedDeltas() {
|
||||
this.pendingDeltas.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ByteBuf getQueuedDeltaPayload() throws IOException {
|
||||
if (this.pendingDeltas.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ByteBuf deltaPayload = Unpooled.buffer();
|
||||
deltaPayload.writeInt(this.pendingDeltas.size());
|
||||
for (final D delta : this.pendingDeltas) {
|
||||
this.deltaCodec.writeDelta(deltaPayload, delta);
|
||||
}
|
||||
return deltaPayload;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T applyDeltaPayload(final ByteBuf buf) throws IOException {
|
||||
T result = this.copyValue(this.getCurrentValue());
|
||||
final int deltaCount = buf.readInt();
|
||||
if (deltaCount <= 0) {
|
||||
throw new IOException("Invalid delta count " + deltaCount + " for " + this.fullKey + ".");
|
||||
}
|
||||
|
||||
for (int i = 0; i < deltaCount; i++) {
|
||||
result = this.deltaCodec.applyDelta(result, buf);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package appeng.container.sync.handlers;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import appeng.container.sync.AbstractSyncHandler;
|
||||
import appeng.container.sync.SyncDirection;
|
||||
import appeng.container.sync.SyncEndpoint;
|
||||
import appeng.container.sync.SyncManager;
|
||||
import appeng.container.sync.SyncMode;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class DoubleSyncHandler extends AbstractSyncHandler {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface DoubleChangeListener {
|
||||
|
||||
void onChange(double oldValue, double newValue);
|
||||
}
|
||||
|
||||
private double value;
|
||||
private double lastSentValue;
|
||||
private boolean hasLastSentValue;
|
||||
private @Nullable DoubleChangeListener clientChangeListener;
|
||||
private @Nullable DoubleChangeListener serverChangeListener;
|
||||
|
||||
public DoubleSyncHandler(final SyncManager manager, final String key, final String fullKey,
|
||||
final SyncDirection direction) {
|
||||
super(manager, key, fullKey, direction);
|
||||
}
|
||||
|
||||
public double get() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeds the local value without marking it dirty or scheduling it to be sent.
|
||||
*/
|
||||
public void setLocalValue(final double value) {
|
||||
this.value = value;
|
||||
this.lastSentValue = value;
|
||||
this.hasLastSentValue = true;
|
||||
this.dirty = false;
|
||||
this.clearFullResyncRequest();
|
||||
}
|
||||
|
||||
public void set(final double value) {
|
||||
this.value = value;
|
||||
this.markDirty();
|
||||
}
|
||||
|
||||
public void markDirty() {
|
||||
this.markDirtyInternal();
|
||||
}
|
||||
|
||||
public DoubleSyncHandler onClientChange(final DoubleChangeListener listener) {
|
||||
this.clientChangeListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DoubleSyncHandler onServerChange(final DoubleChangeListener listener) {
|
||||
this.serverChangeListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SyncMode writeUpdate(final SyncEndpoint localEndpoint, final boolean initialSync, final ByteBuf buf) {
|
||||
final boolean forceFullResync = this.shouldForceFullResync();
|
||||
if (!this.canSendFrom(localEndpoint)) {
|
||||
return null;
|
||||
}
|
||||
if (initialSync) {
|
||||
if (!this.sendsInitialStateFrom(localEndpoint)) {
|
||||
return null;
|
||||
}
|
||||
} else if (!this.dirty && !forceFullResync) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!initialSync && !forceFullResync
|
||||
&& this.hasLastSentValue
|
||||
&& Double.doubleToLongBits(this.lastSentValue) == Double.doubleToLongBits(this.value)) {
|
||||
this.dirty = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
buf.writeDouble(this.value);
|
||||
this.lastSentValue = this.value;
|
||||
this.hasLastSentValue = true;
|
||||
this.dirty = false;
|
||||
this.clearFullResyncRequest();
|
||||
return SyncMode.FULL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readUpdate(final SyncEndpoint remoteEndpoint, final SyncMode mode, final ByteBuf buf)
|
||||
throws IOException {
|
||||
if (mode != SyncMode.FULL) {
|
||||
throw new IOException("DoubleSyncHandler only supports full updates.");
|
||||
}
|
||||
|
||||
final double oldValue = this.value;
|
||||
final double newValue = buf.readDouble();
|
||||
this.value = newValue;
|
||||
this.lastSentValue = newValue;
|
||||
this.hasLastSentValue = true;
|
||||
this.dirty = false;
|
||||
|
||||
if (remoteEndpoint == SyncEndpoint.SERVER) {
|
||||
if (this.clientChangeListener != null) {
|
||||
this.clientChangeListener.onChange(oldValue, newValue);
|
||||
}
|
||||
} else if (this.serverChangeListener != null) {
|
||||
this.serverChangeListener.onChange(oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTypeKey() {
|
||||
return "double";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package appeng.container.sync.handlers;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import appeng.container.sync.AbstractSyncHandler;
|
||||
import appeng.container.sync.SyncDirection;
|
||||
import appeng.container.sync.SyncEndpoint;
|
||||
import appeng.container.sync.SyncManager;
|
||||
import appeng.container.sync.SyncMode;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class IntSyncHandler extends AbstractSyncHandler {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface IntChangeListener {
|
||||
|
||||
void onChange(int oldValue, int newValue);
|
||||
}
|
||||
|
||||
private int value;
|
||||
private int lastSentValue;
|
||||
private boolean hasLastSentValue;
|
||||
private @Nullable IntChangeListener clientChangeListener;
|
||||
private @Nullable IntChangeListener serverChangeListener;
|
||||
|
||||
public IntSyncHandler(final SyncManager manager, final String key, final String fullKey,
|
||||
final SyncDirection direction) {
|
||||
super(manager, key, fullKey, direction);
|
||||
}
|
||||
|
||||
public int get() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeds the local value without marking it dirty or scheduling it to be sent.
|
||||
*/
|
||||
public void setLocalValue(final int value) {
|
||||
this.value = value;
|
||||
this.lastSentValue = value;
|
||||
this.hasLastSentValue = true;
|
||||
this.dirty = false;
|
||||
this.clearFullResyncRequest();
|
||||
}
|
||||
|
||||
public void set(final int value) {
|
||||
this.value = value;
|
||||
this.markDirty();
|
||||
}
|
||||
|
||||
public void markDirty() {
|
||||
this.markDirtyInternal();
|
||||
}
|
||||
|
||||
public IntSyncHandler onClientChange(final IntChangeListener listener) {
|
||||
this.clientChangeListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IntSyncHandler onServerChange(final IntChangeListener listener) {
|
||||
this.serverChangeListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SyncMode writeUpdate(final SyncEndpoint localEndpoint, final boolean initialSync, final ByteBuf buf) {
|
||||
final boolean forceFullResync = this.shouldForceFullResync();
|
||||
if (!this.canSendFrom(localEndpoint)) {
|
||||
return null;
|
||||
}
|
||||
if (initialSync) {
|
||||
if (!this.sendsInitialStateFrom(localEndpoint)) {
|
||||
return null;
|
||||
}
|
||||
} else if (!this.dirty && !forceFullResync) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!initialSync && !forceFullResync && this.hasLastSentValue && this.lastSentValue == this.value) {
|
||||
this.dirty = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
buf.writeInt(this.value);
|
||||
this.lastSentValue = this.value;
|
||||
this.hasLastSentValue = true;
|
||||
this.dirty = false;
|
||||
this.clearFullResyncRequest();
|
||||
return SyncMode.FULL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readUpdate(final SyncEndpoint remoteEndpoint, final SyncMode mode, final ByteBuf buf)
|
||||
throws IOException {
|
||||
if (mode != SyncMode.FULL) {
|
||||
throw new IOException("IntSyncHandler only supports full updates.");
|
||||
}
|
||||
|
||||
final int oldValue = this.value;
|
||||
final int newValue = buf.readInt();
|
||||
this.value = newValue;
|
||||
this.lastSentValue = newValue;
|
||||
this.hasLastSentValue = true;
|
||||
this.dirty = false;
|
||||
|
||||
if (remoteEndpoint == SyncEndpoint.SERVER) {
|
||||
if (this.clientChangeListener != null) {
|
||||
this.clientChangeListener.onChange(oldValue, newValue);
|
||||
}
|
||||
} else if (this.serverChangeListener != null) {
|
||||
this.serverChangeListener.onChange(oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTypeKey() {
|
||||
return "int";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package appeng.container.sync.handlers;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import appeng.container.sync.AbstractSyncHandler;
|
||||
import appeng.container.sync.SyncDirection;
|
||||
import appeng.container.sync.SyncEndpoint;
|
||||
import appeng.container.sync.SyncManager;
|
||||
import appeng.container.sync.SyncMode;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class LongSyncHandler extends AbstractSyncHandler {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface LongChangeListener {
|
||||
|
||||
void onChange(long oldValue, long newValue);
|
||||
}
|
||||
|
||||
private long value;
|
||||
private long lastSentValue;
|
||||
private boolean hasLastSentValue;
|
||||
private @Nullable LongChangeListener clientChangeListener;
|
||||
private @Nullable LongChangeListener serverChangeListener;
|
||||
|
||||
public LongSyncHandler(final SyncManager manager, final String key, final String fullKey,
|
||||
final SyncDirection direction) {
|
||||
super(manager, key, fullKey, direction);
|
||||
}
|
||||
|
||||
public long get() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeds the local value without marking it dirty or scheduling it to be sent.
|
||||
*/
|
||||
public void setLocalValue(final long value) {
|
||||
this.value = value;
|
||||
this.lastSentValue = value;
|
||||
this.hasLastSentValue = true;
|
||||
this.dirty = false;
|
||||
this.clearFullResyncRequest();
|
||||
}
|
||||
|
||||
public void set(final long value) {
|
||||
this.value = value;
|
||||
this.markDirty();
|
||||
}
|
||||
|
||||
public void markDirty() {
|
||||
this.markDirtyInternal();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public LongSyncHandler onClientChange(final LongChangeListener listener) {
|
||||
this.clientChangeListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public LongSyncHandler onServerChange(final LongChangeListener listener) {
|
||||
this.serverChangeListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SyncMode writeUpdate(final SyncEndpoint localEndpoint, final boolean initialSync, final ByteBuf buf) {
|
||||
final boolean forceFullResync = this.shouldForceFullResync();
|
||||
if (!this.canSendFrom(localEndpoint)) {
|
||||
return null;
|
||||
}
|
||||
if (initialSync) {
|
||||
if (!this.sendsInitialStateFrom(localEndpoint)) {
|
||||
return null;
|
||||
}
|
||||
} else if (!this.dirty && !forceFullResync) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!initialSync && !forceFullResync && this.hasLastSentValue && this.lastSentValue == this.value) {
|
||||
this.dirty = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
buf.writeLong(this.value);
|
||||
this.lastSentValue = this.value;
|
||||
this.hasLastSentValue = true;
|
||||
this.dirty = false;
|
||||
this.clearFullResyncRequest();
|
||||
return SyncMode.FULL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readUpdate(final SyncEndpoint remoteEndpoint, final SyncMode mode, final ByteBuf buf)
|
||||
throws IOException {
|
||||
if (mode != SyncMode.FULL) {
|
||||
throw new IOException("LongSyncHandler only supports full updates.");
|
||||
}
|
||||
|
||||
final long oldValue = this.value;
|
||||
final long newValue = buf.readLong();
|
||||
this.value = newValue;
|
||||
this.lastSentValue = newValue;
|
||||
this.hasLastSentValue = true;
|
||||
this.dirty = false;
|
||||
|
||||
if (remoteEndpoint == SyncEndpoint.SERVER) {
|
||||
if (this.clientChangeListener != null) {
|
||||
this.clientChangeListener.onChange(oldValue, newValue);
|
||||
}
|
||||
} else if (this.serverChangeListener != null) {
|
||||
this.serverChangeListener.onChange(oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTypeKey() {
|
||||
return "long";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package appeng.container.sync.handlers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import appeng.container.sync.AbstractSyncHandler;
|
||||
import appeng.container.sync.DeltaSyncCodec;
|
||||
import appeng.container.sync.SyncCodec;
|
||||
import appeng.container.sync.SyncDirection;
|
||||
import appeng.container.sync.SyncEndpoint;
|
||||
import appeng.container.sync.SyncManager;
|
||||
import appeng.container.sync.SyncMode;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
public class ObjectSyncHandler<T> extends AbstractSyncHandler {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ObjectChangeListener<T> {
|
||||
|
||||
void onChange(T oldValue, T newValue);
|
||||
}
|
||||
|
||||
private final SyncCodec<T> codec;
|
||||
private T value;
|
||||
private T lastSentSnapshot;
|
||||
private boolean hasLastSentSnapshot;
|
||||
private @Nullable ObjectChangeListener<T> clientChangeListener;
|
||||
private @Nullable ObjectChangeListener<T> serverChangeListener;
|
||||
|
||||
public ObjectSyncHandler(final SyncManager manager, final String key, final String fullKey,
|
||||
final SyncDirection direction, final SyncCodec<T> codec, final T initialValue) {
|
||||
super(manager, key, fullKey, direction);
|
||||
this.codec = Objects.requireNonNull(codec, "codec");
|
||||
this.value = initialValue;
|
||||
this.lastSentSnapshot = this.copyValue(initialValue);
|
||||
this.hasLastSentSnapshot = true;
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
public T get() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void set(final T value) {
|
||||
this.value = value;
|
||||
this.clearQueuedDeltas();
|
||||
this.markDirty();
|
||||
}
|
||||
|
||||
public void markDirty() {
|
||||
this.markDirtyInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies an in-place change to the current value and marks the handler dirty.
|
||||
*/
|
||||
public void mutate(final Consumer<T> mutator) {
|
||||
if (this.value == null) {
|
||||
throw new IllegalStateException("Cannot mutate a null sync value: " + this.fullKey);
|
||||
}
|
||||
|
||||
mutator.accept(this.value);
|
||||
this.clearQueuedDeltas();
|
||||
this.markDirty();
|
||||
}
|
||||
|
||||
public @NotNull ObjectSyncHandler<T> onClientChange(final ObjectChangeListener<T> listener) {
|
||||
this.clientChangeListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NotNull ObjectSyncHandler<T> onServerChange(final ObjectChangeListener<T> listener) {
|
||||
this.serverChangeListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SyncMode writeUpdate(final SyncEndpoint localEndpoint, final boolean initialSync, final ByteBuf buf)
|
||||
throws IOException {
|
||||
final boolean forceFullResync = this.shouldForceFullResync();
|
||||
if (!this.canSendFrom(localEndpoint)) {
|
||||
return null;
|
||||
}
|
||||
if (initialSync) {
|
||||
if (!this.sendsInitialStateFrom(localEndpoint)) {
|
||||
return null;
|
||||
}
|
||||
} else if (!this.dirty && !forceFullResync) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!initialSync && !forceFullResync
|
||||
&& this.hasLastSentSnapshot
|
||||
&& this.valuesEqual(this.value, this.lastSentSnapshot)) {
|
||||
this.clearQueuedDeltas();
|
||||
this.dirty = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
SyncMode mode = SyncMode.FULL;
|
||||
ByteBuf payload = buf;
|
||||
|
||||
if (!initialSync && !forceFullResync
|
||||
&& this.codec instanceof DeltaSyncCodec<?, ?>
|
||||
&& this.hasLastSentSnapshot) {
|
||||
final ByteBuf fullPayload = Unpooled.buffer();
|
||||
this.writeFull(fullPayload, this.value);
|
||||
|
||||
final ByteBuf deltaPayload = this.writeDeltaPayload();
|
||||
if (deltaPayload != null && deltaPayload.readableBytes() < fullPayload.readableBytes()) {
|
||||
mode = SyncMode.DELTA;
|
||||
payload = deltaPayload;
|
||||
} else {
|
||||
payload = fullPayload;
|
||||
}
|
||||
} else {
|
||||
this.writeFull(payload, this.value);
|
||||
}
|
||||
|
||||
if (payload != buf) {
|
||||
buf.writeBytes(payload, payload.readerIndex(), payload.readableBytes());
|
||||
}
|
||||
|
||||
this.lastSentSnapshot = this.copyValue(this.value);
|
||||
this.hasLastSentSnapshot = true;
|
||||
this.clearQueuedDeltas();
|
||||
this.dirty = false;
|
||||
this.clearFullResyncRequest();
|
||||
return mode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readUpdate(final SyncEndpoint remoteEndpoint, final SyncMode mode, final ByteBuf buf)
|
||||
throws IOException {
|
||||
final T oldValue = this.value;
|
||||
final T newValue;
|
||||
|
||||
this.clearQueuedDeltas();
|
||||
|
||||
if (mode == SyncMode.FULL) {
|
||||
newValue = this.readFull(buf);
|
||||
} else if (this.codec instanceof DeltaSyncCodec<?, ?>) {
|
||||
try {
|
||||
newValue = this.applyDeltaPayload(buf);
|
||||
} catch (final IOException e) {
|
||||
// CLIENT_TO_SERVER cannot push a full authoritative state from the server,
|
||||
// so ask the client to resend its current full value instead.
|
||||
if (this.getDirection() == SyncDirection.CLIENT_TO_SERVER || remoteEndpoint == SyncEndpoint.SERVER) {
|
||||
this.requestRemoteFullResync();
|
||||
} else {
|
||||
// SERVER_TO_CLIENT and BIDIRECTIONAL can recover by sending the server state.
|
||||
this.requestFullResync();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
throw new IOException("ObjectSyncHandler for " + this.fullKey + " does not support delta updates.");
|
||||
}
|
||||
|
||||
this.value = newValue;
|
||||
this.lastSentSnapshot = this.copyValue(newValue);
|
||||
this.hasLastSentSnapshot = true;
|
||||
this.dirty = false;
|
||||
|
||||
if (remoteEndpoint == SyncEndpoint.SERVER) {
|
||||
if (this.clientChangeListener != null) {
|
||||
this.clientChangeListener.onChange(oldValue, newValue);
|
||||
}
|
||||
} else if (this.serverChangeListener != null) {
|
||||
this.serverChangeListener.onChange(oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTypeKey() {
|
||||
return this.codec.getTypeKey();
|
||||
}
|
||||
|
||||
protected final @Nullable T getCurrentValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
protected final void setCurrentValue(@Nullable final T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private void writeFull(final ByteBuf buf, @Nullable final T value) throws IOException {
|
||||
buf.writeBoolean(value != null);
|
||||
if (value != null) {
|
||||
this.codec.write(buf, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private T readFull(final ByteBuf buf) throws IOException {
|
||||
if (!buf.readBoolean()) {
|
||||
return null;
|
||||
}
|
||||
return this.codec.read(buf);
|
||||
}
|
||||
|
||||
protected final @Nullable T copyValue(@Nullable final T value) {
|
||||
return value == null ? null : this.codec.copy(value);
|
||||
}
|
||||
|
||||
private boolean valuesEqual(@Nullable final T a, @Nullable final T b) {
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
if (a == null || b == null) {
|
||||
return false;
|
||||
}
|
||||
return this.codec.valuesEqual(a, b);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected ByteBuf writeDeltaPayload() throws IOException {
|
||||
final ByteBuf queuedDeltaPayload = this.getQueuedDeltaPayload();
|
||||
if (queuedDeltaPayload != null) {
|
||||
return queuedDeltaPayload;
|
||||
}
|
||||
|
||||
final DeltaSyncCodec<T, Object> deltaCodec = (DeltaSyncCodec<T, Object>) this.codec;
|
||||
final T previous = this.lastSentSnapshot;
|
||||
final T current = this.value;
|
||||
if (previous == null || current == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final List<Object> deltas = deltaCodec.diff(previous, current);
|
||||
if (deltas == null || deltas.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ByteBuf deltaPayload = Unpooled.buffer();
|
||||
deltaPayload.writeInt(deltas.size());
|
||||
for (final Object delta : deltas) {
|
||||
deltaCodec.writeDelta(deltaPayload, delta);
|
||||
}
|
||||
return deltaPayload;
|
||||
}
|
||||
|
||||
protected T applyDeltaPayload(final ByteBuf buf) throws IOException {
|
||||
final DeltaSyncCodec<T, Object> deltaCodec = (DeltaSyncCodec<T, Object>) this.codec;
|
||||
T result = this.copyValue(this.value);
|
||||
final int deltaCount = buf.readInt();
|
||||
if (deltaCount <= 0) {
|
||||
throw new IOException("Invalid delta count " + deltaCount + " for " + this.fullKey + ".");
|
||||
}
|
||||
|
||||
for (int i = 0; i < deltaCount; i++) {
|
||||
result = deltaCodec.applyDelta(result, buf);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void clearQueuedDeltas() {}
|
||||
|
||||
protected @Nullable ByteBuf getQueuedDeltaPayload() throws IOException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import appeng.core.sync.packets.PacketCompassRequest;
|
||||
import appeng.core.sync.packets.PacketCompassResponse;
|
||||
import appeng.core.sync.packets.PacketCompressedNBT;
|
||||
import appeng.core.sync.packets.PacketConfigButton;
|
||||
import appeng.core.sync.packets.PacketContainerSync;
|
||||
import appeng.core.sync.packets.PacketCraftRequest;
|
||||
import appeng.core.sync.packets.PacketCraftingCPUTableUpdate;
|
||||
import appeng.core.sync.packets.PacketCraftingCompleteNotification;
|
||||
@@ -94,6 +95,7 @@ public class AppEngPacketHandlerBase {
|
||||
PACKET_TRANSITION_EFFECT(PacketTransitionEffect.class),
|
||||
|
||||
PACKET_GUI_DATA_SYNC(PacketGuiDataSync.class),
|
||||
PACKET_CONTAINER_SYNC(PacketContainerSync.class),
|
||||
|
||||
PACKET_CLICK(PacketClick.class),
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package appeng.core.sync.packets;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
|
||||
import appeng.container.AEBaseContainer;
|
||||
import appeng.container.sync.SyncEndpoint;
|
||||
import appeng.core.sync.AppEngPacket;
|
||||
import appeng.core.sync.network.INetworkInfo;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
public class PacketContainerSync extends AppEngPacket {
|
||||
|
||||
private final int windowId;
|
||||
private final byte[] data;
|
||||
|
||||
public PacketContainerSync(final ByteBuf buf) {
|
||||
this.windowId = buf.readInt();
|
||||
this.data = new byte[buf.readableBytes()];
|
||||
buf.readBytes(this.data);
|
||||
}
|
||||
|
||||
public PacketContainerSync(final int windowId, final ByteBuf data) {
|
||||
this.windowId = windowId;
|
||||
this.data = null;
|
||||
|
||||
final ByteBuf buf = Unpooled.buffer();
|
||||
buf.writeInt(this.getPacketID());
|
||||
buf.writeInt(windowId);
|
||||
buf.writeBytes(data, data.readerIndex(), data.readableBytes());
|
||||
this.configureWrite(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientPacketData(final INetworkInfo network, final AppEngPacket packet, final EntityPlayer player) {
|
||||
if (player.openContainer instanceof AEBaseContainer container && container.windowId == this.windowId) {
|
||||
container.receiveSyncData(SyncEndpoint.SERVER, Unpooled.wrappedBuffer(this.data));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverPacketData(final INetworkInfo network, final AppEngPacket packet, final EntityPlayer player) {
|
||||
if (player.openContainer instanceof AEBaseContainer container && container.windowId == this.windowId) {
|
||||
container.receiveSyncData(SyncEndpoint.CLIENT, Unpooled.wrappedBuffer(this.data));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ import appeng.container.implementations.ContainerCellWorkbench;
|
||||
import appeng.container.implementations.ContainerCraftConfirm;
|
||||
import appeng.container.implementations.ContainerCraftingCPU;
|
||||
import appeng.container.implementations.ContainerInterface;
|
||||
import appeng.container.implementations.ContainerLevelEmitter;
|
||||
import appeng.container.implementations.ContainerMEMonitorable;
|
||||
import appeng.container.implementations.ContainerNetworkStatus;
|
||||
import appeng.container.implementations.ContainerNetworkTool;
|
||||
@@ -137,10 +136,6 @@ public class PacketValueConfig extends AppEngPacket {
|
||||
pc.setPriority(Integer.parseInt(this.Value), player);
|
||||
} else if (this.Name.equals("OreFilter") && c instanceof ContainerOreFilter fc) {
|
||||
fc.setFilter(this.Value);
|
||||
} else if (this.Name.equals("LevelEmitter.Value") && c instanceof ContainerLevelEmitter lvc) {
|
||||
lvc.setLevel(Long.parseLong(this.Value), player);
|
||||
} else if (this.Name.equals("LevelEmitter.TypeFilter") && c instanceof ContainerLevelEmitter lvc) {
|
||||
lvc.toggleTypeFilter(this.Value, player);
|
||||
} else if (this.Name.startsWith("PatternTerminal.") && c instanceof final ContainerPatternTerm cpt) {
|
||||
switch (this.Name) {
|
||||
case "PatternTerminal.CraftMode" -> cpt.getPatternTerminal().setCraftingRecipe(this.Value.equals("1"));
|
||||
|
||||
@@ -103,6 +103,14 @@ public class AEStackTypeFilter implements IGuiPacketWritable {
|
||||
return this.filters.getBoolean(type);
|
||||
}
|
||||
|
||||
public void copyFrom(@NotNull final AEStackTypeFilter other) {
|
||||
this.filters.clear();
|
||||
this.filters.putAll(createDefaultMap());
|
||||
for (Reference2BooleanMap.Entry<IAEStackType<?>> entry : other.filters.reference2BooleanEntrySet()) {
|
||||
this.filters.put(entry.getKey(), entry.getBooleanValue());
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Reference2BooleanMap<IAEStackType<?>> getImmutableFilters() {
|
||||
return Reference2BooleanMaps.unmodifiable(new Reference2BooleanOpenHashMap<>(this.filters));
|
||||
|
||||
Reference in new issue
Block a user