From 2df36137c2c995b5d12f1e8b7db101b47650b570 Mon Sep 17 00:00:00 2001 From: ichttt Date: Sat, 6 May 2017 21:19:01 +0200 Subject: [PATCH] Add a configurable delay when unloading dimensions, fixes #3455 (#3679) --- .../common/DimensionManager.java | 92 +++++++++++++------ .../common/ForgeModContainer.java | 8 ++ .../resources/assets/forge/lang/en_US.lang | 2 + 3 files changed, 75 insertions(+), 27 deletions(-) diff --git a/src/main/java/net/minecraftforge/common/DimensionManager.java b/src/main/java/net/minecraftforge/common/DimensionManager.java index 11c4f48270..9012c4b016 100644 --- a/src/main/java/net/minecraftforge/common/DimensionManager.java +++ b/src/main/java/net/minecraftforge/common/DimensionManager.java @@ -30,6 +30,8 @@ import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntListIterator; import org.apache.logging.log4j.Level; import com.google.common.collect.HashMultiset; @@ -47,7 +49,6 @@ import net.minecraft.world.WorldProvider; import net.minecraft.world.WorldServer; import net.minecraft.world.WorldServerMulti; import net.minecraft.world.storage.ISaveHandler; -import net.minecraft.world.storage.SaveHandler; import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.common.FMLLog; @@ -56,10 +57,21 @@ import javax.annotation.Nullable; public class DimensionManager { + private static class Dimension + { + private final DimensionType type; + private int ticksWaited; + private Dimension(DimensionType type) + { + this.type = type; + this.ticksWaited = 0; + } + } + private static Hashtable worlds = new Hashtable(); private static boolean hasInit = false; - private static Hashtable dimensions = new Hashtable(); - private static ArrayList unloadQueue = new ArrayList(); + private static Hashtable dimensions = new Hashtable(); + private static IntArrayList unloadQueue = new IntArrayList(); private static BitSet dimensionMap = new BitSet(Long.SIZE << 4); private static ConcurrentMap weakWorldMap = new MapMaker().weakKeys().weakValues().makeMap(); private static Multiset leakedWorlds = HashMultiset.create(); @@ -71,9 +83,9 @@ public class DimensionManager { int[] ret = new int[dimensions.size()]; int x = 0; - for (Map.Entry ent : dimensions.entrySet()) + for (Map.Entry ent : dimensions.entrySet()) { - if (ent.getValue() == type) + if (ent.getValue().type == type) { ret[x++] = ent.getKey(); } @@ -103,7 +115,7 @@ public class DimensionManager { throw new IllegalArgumentException(String.format("Failed to register dimension for id %d, One is already registered", id)); } - dimensions.put(id, type); + dimensions.put(id, new Dimension(type)); if (id >= 0) { dimensionMap.set(id); @@ -133,7 +145,7 @@ public class DimensionManager { throw new IllegalArgumentException(String.format("Could not get provider type for dimension %d, does not exist", dim)); } - return dimensions.get(dim); + return dimensions.get(dim).type; } public static WorldProvider getProvider(int dim) @@ -286,39 +298,65 @@ public class DimensionManager } } - public static void unloadWorld(int id) { - unloadQueue.add(id); + /** + * Queues a dimension to unload. + * If the dimension is already queued, it will reset the delay to unload + * @param id The id of the dimension + */ + public static void unloadWorld(int id) + { + if(!unloadQueue.contains(id)) + { + FMLLog.fine("Queueing dimension %s to unload", id); + unloadQueue.add(id); + } + else + { + dimensions.get(id).ticksWaited = 0; + } + } + + public static boolean isWorldQueuedToUnload(int id) + { + return unloadQueue.contains(id); } /* * To be called by the server at the appropriate time, do not call from mod code. */ public static void unloadWorlds(Hashtable worldTickTimes) { - for (int id : unloadQueue) { + IntListIterator queueIterator = unloadQueue.iterator(); + while (queueIterator.hasNext()) { + int id = queueIterator.next(); + Dimension dimension = dimensions.get(id); + if (dimension.ticksWaited < ForgeModContainer.dimensionUnloadQueueDelay) + { + dimension.ticksWaited++; + continue; + } WorldServer w = worlds.get(id); - try { - if (w != null) - { - w.saveAllChunks(true, null); - } - else - { - FMLLog.warning("Unexpected world unload - world %d is already unloaded", id); - } - } catch (MinecraftException e) { + queueIterator.remove(); + dimension.ticksWaited = 0; + if (w == null || !ForgeChunkManager.getPersistentChunksFor(w).isEmpty() || !w.playerEntities.isEmpty() || dimension.type.shouldLoadSpawn()) //Don't unload the world if the status changed + { + FMLLog.fine("Aborting unload for dimension %s as status changed", id); + continue; + } + try + { + w.saveAllChunks(true, null); + } + catch (MinecraftException e) + { e.printStackTrace(); } finally { - if (w != null) - { - MinecraftForge.EVENT_BUS.post(new WorldEvent.Unload(w)); - w.flush(); - setWorld(id, null, w.getMinecraftServer()); - } + MinecraftForge.EVENT_BUS.post(new WorldEvent.Unload(w)); + w.flush(); + setWorld(id, null, w.getMinecraftServer()); } } - unloadQueue.clear(); } /** diff --git a/src/main/java/net/minecraftforge/common/ForgeModContainer.java b/src/main/java/net/minecraftforge/common/ForgeModContainer.java index 516a3b4dbf..ddfc75a7db 100644 --- a/src/main/java/net/minecraftforge/common/ForgeModContainer.java +++ b/src/main/java/net/minecraftforge/common/ForgeModContainer.java @@ -116,6 +116,7 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC public static long java8Reminder = 0; public static boolean disableStairSlabCulling = false; // Also known as the "DontCullStairsBecauseIUseACrappyTexturePackThatBreaksBasicBlockShapesSoICantTrustBasicBlockCulling" flag public static boolean alwaysSetupTerrainOffThread = false; // In RenderGlobal.setupTerrain, always force the chunk render updates to be queued to the thread + public static int dimensionUnloadQueueDelay = 0; public static boolean logCascadingWorldGeneration = true; // see Chunk#logCascadingWorldGeneration() private static Configuration config; @@ -281,6 +282,13 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC prop.setLanguageKey("forge.configgui.logCascadingWorldGeneration"); propOrder.add(prop.getName()); + prop = config.get(Configuration.CATEGORY_GENERAL, "dimensionUnloadQueueDelay", 0, + "The time in ticks the server will wait when a dimension was queued to unload. " + + "This can be useful when rapidly loading and unloading dimensions, like e.g. throwing items through a nether portal a few time per second."); + dimensionUnloadQueueDelay = prop.getInt(0); + prop.setLanguageKey("forge.configgui.dimensionUnloadQueueDelay"); + propOrder.add(prop.getName()); + config.setCategoryPropertyOrder(CATEGORY_GENERAL, propOrder); propOrder = new ArrayList(); diff --git a/src/main/resources/assets/forge/lang/en_US.lang b/src/main/resources/assets/forge/lang/en_US.lang index 31b07225f9..16c4ed4ec9 100644 --- a/src/main/resources/assets/forge/lang/en_US.lang +++ b/src/main/resources/assets/forge/lang/en_US.lang @@ -29,6 +29,8 @@ forge.configgui.clumpingThreshold.tooltip=Controls the number threshold at which forge.configgui.clumpingThreshold=Packet Clumping Threshold forge.configgui.disableVersionCheck.tooltip=Set to true to disable Forge's version check mechanics. Forge queries a small json file on our server for version information. For more details see the ForgeVersion class in our github. forge.configgui.disableVersionCheck=Disable Forge Version Check +forge.configgui.dimensionUnloadQueueDelay=Delay when unloading dimension +forge.configgui.dimensionUnloadQueueDelay.tooltip=The time in ticks the server will wait until unloading a dimension. This can be useful when rapidly loading and unloading dimensions, like e.g. throwing items through a nether portal a few time per second. forge.configgui.enableGlobalConfig=Enable Global Config forge.configgui.forceDuplicateFluidBlockCrash.tooltip=Set this to true to force a crash if more than one block attempts to link back to the same Fluid. forge.configgui.forceDuplicateFluidBlockCrash=Force Dupe Fluid Block Crash