diff --git a/gradle.properties b/gradle.properties
index 0f2f3c1203c..b89012ae610 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -7,12 +7,11 @@ java_version=21
#########################################################
# Minecraft Versions #
#########################################################
-minecraft_release=1.21
-minecraft_version=1.21
-minecraft_version_range=1.21
+minecraft_version=1.21.1
+minecraft_version_range=[1.21.1]
# https://projects.neoforged.net/neoforged/neoforge
-neoforge_version=21.0.146
-neoforge_version_range=[21.0.143,)
+neoforge_version=21.1.1
+neoforge_version_range=[21.1.1,)
#########################################################
# Parchment #
diff --git a/src/main/java/appeng/api/networking/GridServices.java b/src/main/java/appeng/api/networking/GridServices.java
index 5351bd9a8f2..884313a64b0 100644
--- a/src/main/java/appeng/api/networking/GridServices.java
+++ b/src/main/java/appeng/api/networking/GridServices.java
@@ -27,12 +27,16 @@
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.LinkedHashMap;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
+import net.minecraft.world.level.Level;
+
+import appeng.me.helpers.GridServiceContainer;
+
/**
* A registry of grid services to extend grid functionality.
*/
@@ -92,14 +96,37 @@ private static boolean isRegistered(Class> publicInterface) {
*
* This is used by AE2 internally to initialize the services for a grid.
*/
- static Map, IGridServiceProvider> createServices(IGrid g) {
- var result = new LinkedHashMap, IGridServiceProvider>(registry.size());
+ static GridServiceContainer createServices(IGrid g) {
+ var services = new IdentityHashMap, IGridServiceProvider>(registry.size());
+ var serverStartTickServices = new ArrayList(registry.size());
+ var levelStartTickServices = new ArrayList(registry.size());
+ var levelEndTickServices = new ArrayList(registry.size());
+ var serverEndTickServices = new ArrayList(registry.size());
for (var registration : registry) {
- result.put(registration.publicInterface, registration.construct(g, result));
+ var service = registration.construct(g, services);
+ services.put(registration.publicInterface, service);
+
+ if (registration.hasServerStartTick) {
+ serverStartTickServices.add(service);
+ }
+ if (registration.hasLevelStartTick) {
+ levelStartTickServices.add(service);
+ }
+ if (registration.hasLevelEndTick) {
+ levelEndTickServices.add(service);
+ }
+ if (registration.hasServerEndTick) {
+ serverEndTickServices.add(service);
+ }
}
- return result;
+ return new GridServiceContainer(
+ services,
+ serverStartTickServices.toArray(IGridServiceProvider[]::new),
+ levelStartTickServices.toArray(IGridServiceProvider[]::new),
+ levelEndTickServices.toArray(IGridServiceProvider[]::new),
+ serverEndTickServices.toArray(IGridServiceProvider[]::new));
}
private static class GridCacheRegistration {
@@ -114,6 +141,11 @@ private static class GridCacheRegistration {
private final Set> dependencies;
+ private final boolean hasServerStartTick;
+ private final boolean hasLevelStartTick;
+ private final boolean hasLevelEndTick;
+ private final boolean hasServerEndTick;
+
@SuppressWarnings("unchecked")
public GridCacheRegistration(Class implClass, Class> publicInterface) {
this.publicInterface = publicInterface;
@@ -130,6 +162,19 @@ public GridCacheRegistration(Class implClass, Class> publicInterface) {
this.dependencies = Arrays.stream(this.constructorParameterTypes)
.filter(t -> !t.equals(IGrid.class))
.collect(Collectors.toSet());
+
+ try {
+ this.hasServerStartTick = implClass.getMethod("onServerStartTick")
+ .getDeclaringClass() != IGridServiceProvider.class;
+ this.hasLevelStartTick = implClass.getMethod("onLevelStartTick", Level.class)
+ .getDeclaringClass() != IGridServiceProvider.class;
+ this.hasLevelEndTick = implClass.getMethod("onLevelEndTick", Level.class)
+ .getDeclaringClass() != IGridServiceProvider.class;
+ this.hasServerEndTick = implClass.getMethod("onServerEndTick")
+ .getDeclaringClass() != IGridServiceProvider.class;
+ } catch (NoSuchMethodException exception) {
+ throw new RuntimeException("Failed to check which methods the grid service implements", exception);
+ }
}
public IGridServiceProvider construct(IGrid g, Map, IGridServiceProvider> createdServices) {
diff --git a/src/main/java/appeng/api/networking/GridServicesInternal.java b/src/main/java/appeng/api/networking/GridServicesInternal.java
index 1dc48c057a0..1ec2dc67ae4 100644
--- a/src/main/java/appeng/api/networking/GridServicesInternal.java
+++ b/src/main/java/appeng/api/networking/GridServicesInternal.java
@@ -18,14 +18,14 @@
package appeng.api.networking;
-import java.util.Map;
+import appeng.me.helpers.GridServiceContainer;
/**
* Allows access to non-public features of {@link GridServices}.
*/
public class GridServicesInternal {
- public static Map, IGridServiceProvider> createServices(IGrid g) {
+ public static GridServiceContainer createServices(IGrid g) {
return GridServices.createServices(g);
}
diff --git a/src/main/java/appeng/client/guidebook/compiler/tags/RecipeCompiler.java b/src/main/java/appeng/client/guidebook/compiler/tags/RecipeCompiler.java
index 004632b1c7d..f561913ce4a 100644
--- a/src/main/java/appeng/client/guidebook/compiler/tags/RecipeCompiler.java
+++ b/src/main/java/appeng/client/guidebook/compiler/tags/RecipeCompiler.java
@@ -109,6 +109,8 @@ private record RecipeTypeMapping, C extends RecipeInput>(
Function, LytBlock> factory) {
@Nullable
LytBlock tryCreate(RecipeManager recipeManager, Item resultItem) {
+ var registryAccess = Platform.getClientRegistryAccess();
+
// We try to find non-special recipes first then fall back to special
List> fallbackCandidates = new ArrayList<>();
for (var recipe : recipeManager.byType(recipeType)) {
@@ -117,24 +119,14 @@ LytBlock tryCreate(RecipeManager recipeManager, Item resultItem) {
continue;
}
- try {
- if (recipe.value().getResultItem(null).getItem() == resultItem) {
- return factory.apply(recipe);
- }
- } catch (Exception ignored) {
- // :-P
- // Happens if the recipe doesn't accept a null RegistryAccess
+ if (recipe.value().getResultItem(registryAccess).getItem() == resultItem) {
+ return factory.apply(recipe);
}
}
for (var recipe : fallbackCandidates) {
- try {
- if (recipe.value().getResultItem(null).getItem() == resultItem) {
- return factory.apply(recipe);
- }
- } catch (Exception ignored) {
- // :-P
- // Happens if the recipe doesn't accept a null RegistryAccess
+ if (recipe.value().getResultItem(registryAccess).getItem() == resultItem) {
+ return factory.apply(recipe);
}
}
diff --git a/src/main/java/appeng/core/AEConfig.java b/src/main/java/appeng/core/AEConfig.java
index 8ddf39dc00b..8efbca6da60 100644
--- a/src/main/java/appeng/core/AEConfig.java
+++ b/src/main/java/appeng/core/AEConfig.java
@@ -100,8 +100,10 @@ public boolean isSearchModNameInTooltips() {
}
public void setSearchModNameInTooltips(boolean enable) {
- client.searchModNameInTooltips.set(enable);
- client.spec.save();
+ if (enable != client.searchModNameInTooltips.getAsBoolean()) {
+ client.searchModNameInTooltips.set(enable);
+ client.spec.save();
+ }
}
public boolean isUseExternalSearch() {
@@ -109,8 +111,10 @@ public boolean isUseExternalSearch() {
}
public void setUseExternalSearch(boolean enable) {
- client.useExternalSearch.set(enable);
- client.spec.save();
+ if (enable != client.useExternalSearch.getAsBoolean()) {
+ client.useExternalSearch.set(enable);
+ client.spec.save();
+ }
}
public boolean isClearExternalSearchOnOpen() {
@@ -118,8 +122,10 @@ public boolean isClearExternalSearchOnOpen() {
}
public void setClearExternalSearchOnOpen(boolean enable) {
- client.clearExternalSearchOnOpen.set(enable);
- client.spec.save();
+ if (enable != client.clearExternalSearchOnOpen.getAsBoolean()) {
+ client.clearExternalSearchOnOpen.set(enable);
+ client.spec.save();
+ }
}
public boolean isRememberLastSearch() {
@@ -127,8 +133,10 @@ public boolean isRememberLastSearch() {
}
public void setRememberLastSearch(boolean enable) {
- client.rememberLastSearch.set(enable);
- client.spec.save();
+ if (enable != client.rememberLastSearch.getAsBoolean()) {
+ client.rememberLastSearch.set(enable);
+ client.spec.save();
+ }
}
public boolean isAutoFocusSearch() {
@@ -136,8 +144,10 @@ public boolean isAutoFocusSearch() {
}
public void setAutoFocusSearch(boolean enable) {
- client.autoFocusSearch.set(enable);
- client.spec.save();
+ if (enable != client.autoFocusSearch.getAsBoolean()) {
+ client.autoFocusSearch.set(enable);
+ client.spec.save();
+ }
}
public boolean isSyncWithExternalSearch() {
@@ -145,8 +155,10 @@ public boolean isSyncWithExternalSearch() {
}
public void setSyncWithExternalSearch(boolean enable) {
- client.syncWithExternalSearch.set(enable);
- client.spec.save();
+ if (enable != client.syncWithExternalSearch.getAsBoolean()) {
+ client.syncWithExternalSearch.set(enable);
+ client.spec.save();
+ }
}
public TerminalStyle getTerminalStyle() {
@@ -154,8 +166,10 @@ public TerminalStyle getTerminalStyle() {
}
public void setTerminalStyle(TerminalStyle setting) {
- client.terminalStyle.set(setting);
- client.spec.save();
+ if (setting != client.terminalStyle.get()) {
+ client.terminalStyle.set(setting);
+ client.spec.save();
+ }
}
public double getGridEnergyStoragePerNode() {
@@ -263,8 +277,10 @@ public boolean isShowDebugGuiOverlays() {
}
public void setShowDebugGuiOverlays(boolean enable) {
- client.debugGuiOverlays.set(enable);
- client.spec.save();
+ if (enable != client.debugGuiOverlays.getAsBoolean()) {
+ client.debugGuiOverlays.set(enable);
+ client.spec.save();
+ }
}
public boolean isSpawnPressesInMeteoritesEnabled() {
@@ -308,8 +324,10 @@ public ChannelMode getChannelMode() {
}
public void setChannelModel(ChannelMode mode) {
- common.channels.set(mode);
- client.spec.save();
+ if (mode != common.channels.get()) {
+ common.channels.set(mode);
+ client.spec.save();
+ }
}
public int getPathfindingStepsPerTick() {
@@ -355,8 +373,10 @@ public boolean isPinAutoCraftedItems() {
}
public void setPinAutoCraftedItems(boolean enabled) {
- client.pinAutoCraftedItems.set(enabled);
- client.spec.save();
+ if (enabled != client.pinAutoCraftedItems.getAsBoolean()) {
+ client.pinAutoCraftedItems.set(enabled);
+ client.spec.save();
+ }
}
public boolean isNotifyForFinishedCraftingJobs() {
@@ -364,8 +384,10 @@ public boolean isNotifyForFinishedCraftingJobs() {
}
public void setNotifyForFinishedCraftingJobs(boolean enabled) {
- client.notifyForFinishedCraftingJobs.set(enabled);
- client.spec.save();
+ if (enabled != client.notifyForFinishedCraftingJobs.getAsBoolean()) {
+ client.notifyForFinishedCraftingJobs.set(enabled);
+ client.spec.save();
+ }
}
public boolean isClearGridOnClose() {
@@ -373,8 +395,10 @@ public boolean isClearGridOnClose() {
}
public void setClearGridOnClose(boolean enabled) {
- client.clearGridOnClose.set(enabled);
- client.spec.save();
+ if (enabled != client.clearGridOnClose.getAsBoolean()) {
+ client.clearGridOnClose.set(enabled);
+ client.spec.save();
+ }
}
public double getVibrationChamberBaseEnergyPerFuelTick() {
diff --git a/src/main/java/appeng/crafting/execution/CraftingCpuLogic.java b/src/main/java/appeng/crafting/execution/CraftingCpuLogic.java
index da0a3878c98..2c881cf1453 100644
--- a/src/main/java/appeng/crafting/execution/CraftingCpuLogic.java
+++ b/src/main/java/appeng/crafting/execution/CraftingCpuLogic.java
@@ -49,6 +49,7 @@
import appeng.core.network.clientbound.CraftingJobStatusPacket;
import appeng.crafting.CraftingLink;
import appeng.crafting.inv.ListCraftingInventory;
+import appeng.hooks.ticking.TickHandler;
import appeng.me.cluster.implementations.CraftingCPUCluster;
import appeng.me.service.CraftingService;
@@ -75,6 +76,8 @@ public class CraftingCpuLogic {
*/
private boolean cantStoreItems = false;
+ private long lastModifiedOnTick = TickHandler.instance().getCurrentTick();
+
public CraftingCpuLogic(CraftingCPUCluster cluster) {
this.cluster = cluster;
}
@@ -390,11 +393,16 @@ public void storeItems() {
}
private void postChange(AEKey what) {
+ lastModifiedOnTick = TickHandler.instance().getCurrentTick();
for (var listener : listeners) {
listener.accept(what);
}
}
+ public long getLastModifiedOnTick() {
+ return lastModifiedOnTick;
+ }
+
public boolean hasJob() {
return this.job != null;
}
@@ -509,6 +517,8 @@ public boolean isCantStoreItems() {
}
private void notifyJobOwner(ExecutingCraftingJob job, CraftingJobStatusPacket.Status status) {
+ this.lastModifiedOnTick = TickHandler.instance().getCurrentTick();
+
var playerId = job.playerId;
if (playerId == null) {
return;
diff --git a/src/main/java/appeng/items/tools/powered/powersink/AEBasePoweredItem.java b/src/main/java/appeng/items/tools/powered/powersink/AEBasePoweredItem.java
index e7177d20abc..3b27b85e4c7 100644
--- a/src/main/java/appeng/items/tools/powered/powersink/AEBasePoweredItem.java
+++ b/src/main/java/appeng/items/tools/powered/powersink/AEBasePoweredItem.java
@@ -77,7 +77,7 @@ public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStac
@Override
public int getBarWidth(ItemStack stack) {
double filled = getAECurrentPower(stack) / getAEMaxPower(stack);
- return Mth.clamp((int) (filled * 13), 0, 13);
+ return Mth.clamp((int) Math.round(filled * 13), 0, 13);
}
@Override
diff --git a/src/main/java/appeng/me/Grid.java b/src/main/java/appeng/me/Grid.java
index a60f4b6de8f..1522869dcb3 100644
--- a/src/main/java/appeng/me/Grid.java
+++ b/src/main/java/appeng/me/Grid.java
@@ -48,7 +48,6 @@
import appeng.api.networking.IGridNode;
import appeng.api.networking.IGridNodeListener;
import appeng.api.networking.IGridService;
-import appeng.api.networking.IGridServiceProvider;
import appeng.api.networking.crafting.ICraftingService;
import appeng.api.networking.energy.IEnergyService;
import appeng.api.networking.events.GridEvent;
@@ -58,6 +57,7 @@
import appeng.api.networking.ticking.ITickManager;
import appeng.core.AELog;
import appeng.hooks.ticking.TickHandler;
+import appeng.me.helpers.GridServiceContainer;
import appeng.me.service.P2PService;
import appeng.parts.AEBasePart;
import appeng.util.IDebugExportable;
@@ -71,7 +71,7 @@ public class Grid implements IGrid {
private static int nextSerial = 0;
private final SetMultimap, IGridNode> machines = MultimapBuilder.hashKeys().hashSetValues().build();
- private final Map, IGridServiceProvider> services;
+ private final GridServiceContainer services;
// Becomes null after the last node has left the grid.
@Nullable
private GridNode pivot;
@@ -103,17 +103,13 @@ int getPriority() {
return this.priority;
}
- Collection getProviders() {
- return this.services.values();
- }
-
@Override
public int size() {
return this.machines.size();
}
void remove(GridNode gridNode) {
- for (var c : this.services.values()) {
+ for (var c : services.services().values()) {
c.removeNode(gridNode);
}
@@ -137,13 +133,13 @@ void add(GridNode gridNode, @Nullable CompoundTag savedData) {
// track node.
this.machines.put(gridNode.getOwner().getClass(), gridNode);
- for (var service : this.services.values()) {
+ for (var service : services.services().values()) {
service.addNode(gridNode, savedData);
}
}
void saveNodeData(GridNode gridNode, CompoundTag savedData) {
- for (var service : this.services.values()) {
+ for (var service : services.services().values()) {
service.saveNodeData(gridNode, savedData);
}
}
@@ -151,7 +147,7 @@ void saveNodeData(GridNode gridNode, CompoundTag savedData) {
@SuppressWarnings("unchecked")
@Override
public C getService(Class iface) {
- var service = this.services.get(iface);
+ var service = this.services.services().get(iface);
if (service == null) {
throw new IllegalArgumentException("Service " + iface + " is not registered");
}
@@ -224,7 +220,7 @@ public void onServerStartTick() {
return;
}
- for (var gc : this.services.values()) {
+ for (var gc : this.services.serverStartTickServices()) {
gc.onServerStartTick();
}
}
@@ -234,7 +230,7 @@ public void onLevelStartTick(Level level) {
return;
}
- for (var gc : this.services.values()) {
+ for (var gc : this.services.levelStartTickServices()) {
gc.onLevelStartTick(level);
}
}
@@ -244,7 +240,7 @@ public void onLevelEndTick(Level level) {
return;
}
- for (var gc : this.services.values()) {
+ for (var gc : this.services.levelEndtickServices()) {
gc.onLevelEndTick(level);
}
}
@@ -254,7 +250,7 @@ public void onServerEndTick() {
return;
}
- for (var gc : this.services.values()) {
+ for (var gc : this.services.serverEndTickServices()) {
gc.onServerEndTick();
}
}
@@ -354,7 +350,7 @@ public void export(JsonWriter jsonWriter) throws IOException {
jsonWriter.name("services");
jsonWriter.beginObject();
- for (var entry : services.entrySet()) {
+ for (var entry : services.services().entrySet()) {
jsonWriter.name(getServiceExportKey(entry.getKey()));
jsonWriter.beginObject();
entry.getValue().debugDump(jsonWriter, registries);
diff --git a/src/main/java/appeng/me/helpers/GridServiceContainer.java b/src/main/java/appeng/me/helpers/GridServiceContainer.java
new file mode 100644
index 00000000000..7c2f17b21ff
--- /dev/null
+++ b/src/main/java/appeng/me/helpers/GridServiceContainer.java
@@ -0,0 +1,13 @@
+package appeng.me.helpers;
+
+import java.util.Map;
+
+import appeng.api.networking.IGridServiceProvider;
+
+public record GridServiceContainer(
+ Map, IGridServiceProvider> services,
+ IGridServiceProvider[] serverStartTickServices,
+ IGridServiceProvider[] levelStartTickServices,
+ IGridServiceProvider[] levelEndtickServices,
+ IGridServiceProvider[] serverEndTickServices) {
+}
diff --git a/src/main/java/appeng/me/service/CraftingService.java b/src/main/java/appeng/me/service/CraftingService.java
index 63ad0f4013b..24ecdd70aa5 100644
--- a/src/main/java/appeng/me/service/CraftingService.java
+++ b/src/main/java/appeng/me/service/CraftingService.java
@@ -71,6 +71,7 @@
import appeng.crafting.CraftingLink;
import appeng.crafting.CraftingLinkNexus;
import appeng.crafting.execution.CraftingSubmitResult;
+import appeng.hooks.ticking.TickHandler;
import appeng.me.cluster.implementations.CraftingCPUCluster;
import appeng.me.helpers.InterestManager;
import appeng.me.helpers.StackWatcher;
@@ -123,11 +124,15 @@ public class CraftingService implements ICraftingService, IGridServiceProvider {
private final IEnergyService energyGrid;
private final Set currentlyCrafting = new HashSet<>();
private final Set currentlyCraftable = new HashSet<>();
+ private long lastProcessedCraftingLogicChangeTick;
+ private long lastProcessedCraftableChangeTick;
private boolean updateList = false;
public CraftingService(IGrid grid, IStorageService storageGrid, IEnergyService energyGrid) {
this.grid = grid;
this.energyGrid = energyGrid;
+ this.lastProcessedCraftingLogicChangeTick = TickHandler.instance().getCurrentTick();
+ this.lastProcessedCraftableChangeTick = TickHandler.instance().getCurrentTick();
storageGrid.addGlobalStorageProvider(new CraftingServiceStorage(this));
}
@@ -137,44 +142,74 @@ public void onServerEndTick() {
if (this.updateList) {
this.updateList = false;
this.updateCPUClusters();
+ lastProcessedCraftingLogicChangeTick = -1; // Ensure caches below are also updated
}
this.craftingLinks.values().removeIf(nexus -> nexus.isDead(this.grid, this));
- var previouslyCrafting = new HashSet<>(currentlyCrafting);
- var previouslyCraftable = new HashSet<>(currentlyCraftable);
- this.currentlyCrafting.clear();
- this.currentlyCraftable.clear();
-
- for (CraftingCPUCluster cpu : this.craftingCPUClusters) {
+ long latestChange = 0;
+ for (var cpu : this.craftingCPUClusters) {
cpu.craftingLogic.tickCraftingLogic(energyGrid, this);
- cpu.craftingLogic.getAllWaitingFor(this.currentlyCrafting);
+ latestChange = Math.max(
+ latestChange,
+ cpu.craftingLogic.getLastModifiedOnTick());
}
- currentlyCraftable.addAll(getCraftables(k -> true));
-
- // Notify watchers about items no longer being crafted
- var changed = new HashSet();
- changed.addAll(Sets.difference(previouslyCrafting, currentlyCrafting));
- changed.addAll(Sets.difference(currentlyCrafting, previouslyCrafting));
- for (var what : changed) {
- for (var watcher : interestManager.get(what)) {
- watcher.getHost().onRequestChange(what);
+
+ // There's nothing to do if we weren't crafting anything and we don't have any CPUs that could craft
+ if (latestChange != lastProcessedCraftingLogicChangeTick) {
+ lastProcessedCraftingLogicChangeTick = latestChange;
+
+ Set previouslyCrafting = currentlyCrafting.isEmpty() ? Set.of() : new HashSet<>(currentlyCrafting);
+ this.currentlyCrafting.clear();
+
+ for (var cpu : this.craftingCPUClusters) {
+ cpu.craftingLogic.getAllWaitingFor(this.currentlyCrafting);
}
- for (var watcher : interestManager.getAllStacksWatchers()) {
- watcher.getHost().onRequestChange(what);
+
+ // Notify watchers about items no longer being crafted, but only if there can be changes and there are
+ // watchers
+ if (!interests.isEmpty() && !(previouslyCrafting.isEmpty() && currentlyCrafting.isEmpty())) {
+ var changed = new HashSet();
+ changed.addAll(Sets.difference(previouslyCrafting, currentlyCrafting));
+ changed.addAll(Sets.difference(currentlyCrafting, previouslyCrafting));
+ for (var what : changed) {
+ for (var watcher : interestManager.get(what)) {
+ watcher.getHost().onRequestChange(what);
+ }
+ for (var watcher : interestManager.getAllStacksWatchers()) {
+ watcher.getHost().onRequestChange(what);
+ }
+ }
}
}
- // Notify watchers about items no longer craftable
- var changedCraftable = new HashSet();
- changedCraftable.addAll(Sets.difference(previouslyCraftable, currentlyCraftable));
- changedCraftable.addAll(Sets.difference(currentlyCraftable, previouslyCraftable));
- for (var what : changedCraftable) {
- for (var watcher : interestManager.get(what)) {
- watcher.getHost().onCraftableChange(what);
- }
- for (var watcher : interestManager.getAllStacksWatchers()) {
- watcher.getHost().onCraftableChange(what);
+ // Throttle updates of craftables to once every 10 ticks
+ if (lastProcessedCraftableChangeTick != craftingProviders.getLastModifiedOnTick()) {
+ lastProcessedCraftableChangeTick = craftingProviders.getLastModifiedOnTick();
+
+ // If everything is empty, there's nothing to do
+ if (!currentlyCraftable.isEmpty() || !craftingProviders.getCraftableKeys().isEmpty()
+ || !craftingProviders.getEmittableKeys().isEmpty()) {
+ Set previouslyCraftable = currentlyCraftable.isEmpty() ? Set.of()
+ : new HashSet<>(currentlyCraftable);
+ this.currentlyCraftable.clear();
+ currentlyCraftable.addAll(craftingProviders.getCraftableKeys());
+ currentlyCraftable.addAll(craftingProviders.getEmittableKeys());
+
+ // Only perform the change tracking if there are watchers
+ if (!interests.isEmpty()) {
+ var changedCraftable = new HashSet();
+ changedCraftable.addAll(Sets.difference(previouslyCraftable, currentlyCraftable));
+ changedCraftable.addAll(Sets.difference(currentlyCraftable, previouslyCraftable));
+ for (var what : changedCraftable) {
+ for (var watcher : interestManager.get(what)) {
+ watcher.getHost().onCraftableChange(what);
+ }
+ for (var watcher : interestManager.getAllStacksWatchers()) {
+ watcher.getHost().onCraftableChange(what);
+ }
+ }
+ }
}
}
}
diff --git a/src/main/java/appeng/me/service/TickManagerService.java b/src/main/java/appeng/me/service/TickManagerService.java
index 09cf496cdf7..554b628d47e 100644
--- a/src/main/java/appeng/me/service/TickManagerService.java
+++ b/src/main/java/appeng/me/service/TickManagerService.java
@@ -18,7 +18,7 @@
package appeng.me.service;
-import java.util.HashMap;
+import java.util.IdentityHashMap;
import java.util.LongSummaryStatistics;
import java.util.Map;
import java.util.Objects;
@@ -50,10 +50,10 @@ public class TickManagerService implements ITickManager, IGridServiceProvider {
private static final int TICK_RATE_SPEED_UP_FACTOR = 2;
private static final int TICK_RATE_SLOW_DOWN_FACTOR = 1;
- private final Map alertable = new HashMap<>();
- private final Map sleeping = new HashMap<>();
- private final Map awake = new HashMap<>();
- private final Map> upcomingTicks = new HashMap<>();
+ private final Map alertable = new IdentityHashMap<>();
+ private final Map sleeping = new IdentityHashMap<>();
+ private final Map awake = new IdentityHashMap<>();
+ private final Map> upcomingTicks = new IdentityHashMap<>();
private PriorityQueue currentlyTickingQueue = null;
@@ -70,10 +70,6 @@ public void onServerStartTick() {
this.currentTick++;
}
- @Override
- public void onLevelStartTick(Level level) {
- }
-
@Override
public void onLevelEndTick(Level level) {
this.tickLevelQueue(level);
diff --git a/src/main/java/appeng/me/service/helpers/NetworkCraftingProviders.java b/src/main/java/appeng/me/service/helpers/NetworkCraftingProviders.java
index 0ab6b33dca3..5bfabc0697d 100644
--- a/src/main/java/appeng/me/service/helpers/NetworkCraftingProviders.java
+++ b/src/main/java/appeng/me/service/helpers/NetworkCraftingProviders.java
@@ -23,6 +23,7 @@
import appeng.api.stacks.AEKey;
import appeng.api.stacks.KeyCounter;
import appeng.api.storage.AEKeyFilter;
+import appeng.hooks.ticking.TickHandler;
/**
* Keeps track of the crafting patterns in the network, and related information.
@@ -37,6 +38,11 @@ public class NetworkCraftingProviders {
private final KeyCounter craftableItemsList = new KeyCounter();
private final Map emitableItems = new HashMap<>();
+ private final Set craftableKeys = Collections.unmodifiableSet(craftableItems.keySet());
+ private final Set emittableKeys = Collections.unmodifiableSet(emitableItems.keySet());
+
+ private long lastModifiedOnTick = TickHandler.instance().getCurrentTick();
+
public void addProvider(IGridNode node) {
var provider = node.getService(ICraftingProvider.class);
if (provider != null) {
@@ -46,6 +52,7 @@ public void addProvider(IGridNode node) {
var state = new ProviderState(provider);
state.mount(this);
craftingProviders.put(node, state);
+ setLastModifiedOnTick();
}
}
@@ -55,6 +62,7 @@ public void removeProvider(IGridNode node) {
var state = craftingProviders.remove(node);
if (state != null) {
state.unmount(this);
+ setLastModifiedOnTick();
}
}
}
@@ -78,6 +86,14 @@ public Set getCraftables(AEKeyFilter filter) {
return result;
}
+ public Set getCraftableKeys() {
+ return craftableKeys;
+ }
+
+ public Set getEmittableKeys() {
+ return emittableKeys;
+ }
+
public Collection getCraftingFor(AEKey whatToCraft) {
var patterns = this.craftableItems.get(whatToCraft);
if (patterns != null) {
@@ -208,4 +224,15 @@ private List getSortedPatterns() {
private record PatternInfo(IPatternDetails pattern, ProviderState state) {
}
+
+ private void setLastModifiedOnTick() {
+ lastModifiedOnTick = TickHandler.instance().getCurrentTick();
+ }
+
+ /**
+ * @see TickHandler#getCurrentTick()
+ */
+ public long getLastModifiedOnTick() {
+ return lastModifiedOnTick;
+ }
}
diff --git a/src/main/java/appeng/parts/p2p/P2PTunnelPart.java b/src/main/java/appeng/parts/p2p/P2PTunnelPart.java
index 22ed53ee0b4..84df6c5c591 100644
--- a/src/main/java/appeng/parts/p2p/P2PTunnelPart.java
+++ b/src/main/java/appeng/parts/p2p/P2PTunnelPart.java
@@ -19,7 +19,6 @@
package appeng.parts.p2p;
import java.util.List;
-import java.util.Objects;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
@@ -147,6 +146,30 @@ public boolean useStandardMemoryCard() {
@Override
public boolean onUseItemOn(ItemStack heldItem, Player player, InteractionHand hand, Vec3 pos) {
+ // Attunement via held item replaces the tunnel part with the desired target part type
+ var newType = P2PTunnelAttunement.getTunnelPartByTriggerItem(heldItem);
+ if (!newType.isEmpty() && newType.getItem() != getPartItem()
+ && newType.getItem() instanceof IPartItem> partItem) {
+ var oldOutput = isOutput();
+ var myFreq = getFrequency();
+
+ // If we were able to replace the tunnel part, copy over frequency/output state
+ var tunnel = getHost().replacePart(partItem, getSide(), player, hand);
+ if (!isClientSide()) {
+ if (tunnel instanceof P2PTunnelPart> newTunnel) {
+ newTunnel.setOutput(oldOutput);
+ newTunnel.onTunnelNetworkChange();
+
+ newTunnel.getMainNode().ifPresent(grid -> {
+ P2PService.get(grid).updateFreq(newTunnel, myFreq);
+ });
+ }
+ }
+
+ Platform.notifyBlocksOfNeighbors(getLevel(), getBlockEntity().getBlockPos());
+ return true;
+ }
+
if (isClientSide() || hand == InteractionHand.OFF_HAND) {
return false;
}
@@ -160,8 +183,7 @@ public boolean onUseItemOn(ItemStack heldItem, Player player, InteractionHand ha
final boolean wasOutput = this.isOutput();
this.setOutput(false);
- final boolean needsNewFrequency = wasOutput || this.getFrequency() == 0
- || Objects.equals(storedFrequency, newFreq);
+ var needsNewFrequency = wasOutput || newFreq == 0;
var grid = getMainNode().getGrid();
if (grid != null) {
@@ -207,28 +229,6 @@ public boolean onUseItemOn(ItemStack heldItem, Player player, InteractionHand ha
return false;
}
- // Attunement via held item replaces the tunnel part with the desired target part type
- var newType = P2PTunnelAttunement.getTunnelPartByTriggerItem(heldItem);
- if (!newType.isEmpty() && newType.getItem() != getPartItem()
- && newType.getItem() instanceof IPartItem> partItem) {
- var oldOutput = isOutput();
- var myFreq = getFrequency();
-
- // If we were able to replace the tunnel part, copy over frequency/output state
- var tunnel = getHost().replacePart(partItem, getSide(), player, hand);
- if (tunnel instanceof P2PTunnelPart newTunnel) {
- newTunnel.setOutput(oldOutput);
- newTunnel.onTunnelNetworkChange();
-
- newTunnel.getMainNode().ifPresent(grid -> {
- P2PService.get(grid).updateFreq(newTunnel, myFreq);
- });
- }
-
- Platform.notifyBlocksOfNeighbors(getLevel(), getBlockEntity().getBlockPos());
- return true;
- }
-
return false;
}
diff --git a/src/main/java/appeng/recipes/transform/TransformLogic.java b/src/main/java/appeng/recipes/transform/TransformLogic.java
index 78a219c0463..1bbcf589062 100644
--- a/src/main/java/appeng/recipes/transform/TransformLogic.java
+++ b/src/main/java/appeng/recipes/transform/TransformLogic.java
@@ -23,7 +23,8 @@
import net.neoforged.neoforge.event.AddReloadListenerEvent;
import net.neoforged.neoforge.event.server.ServerStartedEvent;
-import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
public final class TransformLogic {
public static boolean canTransformInFluid(ItemEntity entity, FluidState fluid) {
@@ -55,37 +56,38 @@ public static boolean tryTransform(ItemEntity entity, Predicate missingIngredients = Lists.newArrayList(recipe.ingredients);
- Set selectedEntities = new ReferenceOpenHashSet<>(missingIngredients.size());
+ Reference2IntMap consumedItems = new Reference2IntOpenHashMap<>(missingIngredients.size());
if (recipe.circumstance.isExplosion()) {
if (missingIngredients.stream().noneMatch(i -> i.test(entity.getItem())))
continue;
} else {
- if (!missingIngredients.get(0).test(entity.getItem()))
+ if (!missingIngredients.getFirst().test(entity.getItem()))
continue;
}
for (var itemEntity : itemEntities) {
- final ItemStack other = itemEntity.getItem();
+ var other = itemEntity.getItem();
if (!other.isEmpty()) {
for (var it = missingIngredients.iterator(); it.hasNext();) {
Ingredient ing = it.next();
- if (ing.test(other)) {
- selectedEntities.add(itemEntity);
+ var alreadyClaimed = consumedItems.getInt(itemEntity);
+ if (ing.test(other) && other.getCount() - alreadyClaimed > 0) {
+ consumedItems.merge(itemEntity, 1, Integer::sum);
it.remove();
- break;
}
}
}
}
if (missingIngredients.isEmpty()) {
- var items = new ArrayList(selectedEntities.size());
- for (var e : selectedEntities) {
- items.add(e.getItem().split(1));
+ var items = new ArrayList(consumedItems.size());
+ for (var e : consumedItems.reference2IntEntrySet()) {
+ var itemEntity = e.getKey();
+ items.add(itemEntity.getItem().split(e.getIntValue()));
- if (e.getItem().getCount() <= 0) {
- e.discard();
+ if (itemEntity.getItem().getCount() <= 0) {
+ itemEntity.discard();
}
}
var recipeInput = new TransformRecipeInput(items);
diff --git a/src/test/java/appeng/api/networking/GridServicesTest.java b/src/test/java/appeng/api/networking/GridServicesTest.java
index 9d625a1e583..a5145b263f3 100644
--- a/src/test/java/appeng/api/networking/GridServicesTest.java
+++ b/src/test/java/appeng/api/networking/GridServicesTest.java
@@ -30,6 +30,8 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoSettings;
+import net.minecraft.world.level.Level;
+
import appeng.util.BootstrapMinecraft;
@MockitoSettings
@@ -62,7 +64,7 @@ void restoreRegistry() throws Exception {
@Test
void testEmptyRegistry() {
- var services = GridServices.createServices(grid);
+ var services = GridServices.createServices(grid).services();
assertThat(services).isEmpty();
}
@@ -70,7 +72,7 @@ void testEmptyRegistry() {
void testGridServiceWithDefaultConstructor() {
GridServices.register(PublicInterface.class, GridService1.class);
- var services = GridServices.createServices(grid);
+ var services = GridServices.createServices(grid).services();
assertThat(services).containsOnlyKeys(PublicInterface.class);
assertThat(services.get(PublicInterface.class)).isInstanceOf(GridService1.class);
}
@@ -79,7 +81,7 @@ void testGridServiceWithDefaultConstructor() {
void testGridServiceWithGridDependency() {
GridServices.register(GridService2.class, GridService2.class);
- var services = GridServices.createServices(grid);
+ var services = GridServices.createServices(grid).services();
assertThat(services).containsOnlyKeys(GridService2.class);
var actual = (GridService2) services.get(GridService2.class);
assertThat(actual.grid).isSameAs(grid);
@@ -92,7 +94,7 @@ void testGridServicesWithDependencies() {
GridServices.register(GridService3.class, GridService3.class);
GridServices.register(GridService4.class, GridService4.class);
- var services = GridServices.createServices(grid);
+ var services = GridServices.createServices(grid).services();
assertThat(services).containsOnlyKeys(
PublicInterface.class,
GridService2.class,
@@ -176,4 +178,50 @@ public AmbiguousConstructorClass(GridService2 gc2) {
}
}
+ @Test
+ void testTickingServices() {
+ GridServices.register(ServerStartTickOnly.class, ServerStartTickOnly.class);
+ GridServices.register(LevelStartTickOnly.class, LevelStartTickOnly.class);
+ GridServices.register(LevelEndTickOnly.class, LevelEndTickOnly.class);
+ GridServices.register(ServerEndTickOnly.class, ServerEndTickOnly.class);
+
+ var services = GridServicesInternal.createServices(grid);
+ var map = services.services();
+ assertThat(services.serverStartTickServices())
+ .containsExactly(map.get(ServerStartTickOnly.class));
+ assertThat(services.levelStartTickServices())
+ .containsExactly(map.get(LevelStartTickOnly.class));
+ assertThat(services.levelEndtickServices())
+ .containsExactly(map.get(LevelEndTickOnly.class));
+ assertThat(services.serverEndTickServices())
+ .containsExactly(map.get(ServerEndTickOnly.class));
+ }
+
+ public static class ServerStartTickOnly implements IGridServiceProvider {
+ @Override
+ public void onServerStartTick() {
+ IGridServiceProvider.super.onServerStartTick();
+ }
+ }
+
+ public static class LevelStartTickOnly implements IGridServiceProvider {
+ @Override
+ public void onLevelStartTick(Level level) {
+ IGridServiceProvider.super.onLevelStartTick(level);
+ }
+ }
+
+ public static class LevelEndTickOnly implements IGridServiceProvider {
+ @Override
+ public void onLevelEndTick(Level level) {
+ IGridServiceProvider.super.onLevelEndTick(level);
+ }
+ }
+
+ public static class ServerEndTickOnly implements IGridServiceProvider {
+ @Override
+ public void onServerEndTick() {
+ IGridServiceProvider.super.onServerEndTick();
+ }
+ }
}