Add a configurable delay when unloading dimensions, fixes #3455 (#3679)

This commit is contained in:
ichttt 2017-05-06 21:19:01 +02:00 committed by LexManos
parent 34463690c5
commit 2df36137c2
3 changed files with 75 additions and 27 deletions

View File

@ -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<Integer, WorldServer> worlds = new Hashtable<Integer, WorldServer>();
private static boolean hasInit = false;
private static Hashtable<Integer, DimensionType> dimensions = new Hashtable<Integer, DimensionType>();
private static ArrayList<Integer> unloadQueue = new ArrayList<Integer>();
private static Hashtable<Integer, Dimension> dimensions = new Hashtable<Integer, Dimension>();
private static IntArrayList unloadQueue = new IntArrayList();
private static BitSet dimensionMap = new BitSet(Long.SIZE << 4);
private static ConcurrentMap<World, World> weakWorldMap = new MapMaker().weakKeys().weakValues().<World,World>makeMap();
private static Multiset<Integer> leakedWorlds = HashMultiset.create();
@ -71,9 +83,9 @@ public class DimensionManager
{
int[] ret = new int[dimensions.size()];
int x = 0;
for (Map.Entry<Integer, DimensionType> ent : dimensions.entrySet())
for (Map.Entry<Integer, Dimension> 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<Integer, long[]> 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();
}
/**

View File

@ -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<String>();

View File

@ -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