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(); + } + } }