diff --git a/src/main/java/net/minecraft/network/NetHandlerPlayServer.java b/src/main/java/net/minecraft/network/NetHandlerPlayServer.java index ef29bb8..ec301ae 100644 --- a/src/main/java/net/minecraft/network/NetHandlerPlayServer.java +++ b/src/main/java/net/minecraft/network/NetHandlerPlayServer.java @@ -2,9 +2,11 @@ import com.google.common.base.Charsets; import com.google.common.collect.Lists; + import io.netty.buffer.Unpooled; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; + import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; @@ -12,6 +14,7 @@ import java.util.Iterator; import java.util.Random; import java.util.concurrent.Callable; + import net.minecraft.block.material.Material; import net.minecraft.command.server.CommandBlockLogic; import net.minecraft.crash.CrashReport; @@ -79,8 +82,10 @@ import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.IChatComponent; import net.minecraft.util.IntHashMap; +import net.minecraft.util.MathHelper; import net.minecraft.util.ReportedException; import net.minecraft.world.WorldServer; + import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -89,6 +94,7 @@ import cpw.mods.fml.common.eventhandler.Event; import net.minecraftforge.event.ForgeEventFactory; import net.minecraftforge.event.entity.player.PlayerInteractEvent; + import org.ultramine.permission.MinecraftPermissions; import org.ultramine.server.PermissionHandler; @@ -197,6 +203,9 @@ if (this.hasMoved) { + if(!playerEntity.worldObj.blockExists(MathHelper.floor_double(playerEntity.posX), 64, MathHelper.floor_double(playerEntity.posZ))) + return; + double d1; double d2; double d3; diff --git a/src/main/java/net/minecraft/pathfinding/PathFinder.java b/src/main/java/net/minecraft/pathfinding/PathFinder.java index 9371cd2..f12d2eb 100644 --- a/src/main/java/net/minecraft/pathfinding/PathFinder.java +++ b/src/main/java/net/minecraft/pathfinding/PathFinder.java @@ -262,7 +262,7 @@ { for (int j1 = par3; j1 < par3 + par4PathPoint.zCoord; ++j1) { - Block block = par0Entity.worldObj.getBlock(l, i1, j1); + Block block = par0Entity.worldObj.getBlockIfExists(l, i1, j1); if (block.getMaterial() != Material.air) { diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 0c93d48..cf12421 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -243,6 +243,7 @@ protected void initialWorldChunkLoad() { + /* boolean flag = true; boolean flag1 = true; boolean flag2 = true; @@ -271,7 +272,7 @@ worldserver.theChunkProviderServer.loadChunk(chunkcoordinates.posX + k >> 4, chunkcoordinates.posZ + l >> 4); } } - + */ this.clearCurrentTask(); } diff --git a/src/main/java/net/minecraft/server/management/PlayerManager.java b/src/main/java/net/minecraft/server/management/PlayerManager.java index 69cce6b..5a68b1d 100644 --- a/src/main/java/net/minecraft/server/management/PlayerManager.java +++ b/src/main/java/net/minecraft/server/management/PlayerManager.java @@ -270,7 +270,7 @@ PlayerManager.this.chunkWatcherWithPlayers.remove(this); } - PlayerManager.this.getWorldServer().theChunkProviderServer.unloadChunksIfNotNearSpawn(this.chunkLocation.chunkXPos, this.chunkLocation.chunkZPos); + PlayerManager.this.getWorldServer().theChunkProviderServer.unbindChunk(this.chunkLocation.chunkXPos, this.chunkLocation.chunkZPos); } } } diff --git a/src/main/java/net/minecraft/server/management/ServerConfigurationManager.java b/src/main/java/net/minecraft/server/management/ServerConfigurationManager.java index e63bb08..36960ea 100644 --- a/src/main/java/net/minecraft/server/management/ServerConfigurationManager.java +++ b/src/main/java/net/minecraft/server/management/ServerConfigurationManager.java @@ -276,6 +276,7 @@ func_72375_a(par1EntityPlayerMP, (WorldServer)null); } }); + worldserver.theChunkProviderServer.loadAsyncRadius(cx, cz, 1, IChunkLoadCallback.EMPTY); } for (int i = 0; i < this.playerEntityList.size(); ++i) diff --git a/src/main/java/net/minecraft/world/ChunkCache.java b/src/main/java/net/minecraft/world/ChunkCache.java index 213858b..1d24b2e 100644 --- a/src/main/java/net/minecraft/world/ChunkCache.java +++ b/src/main/java/net/minecraft/world/ChunkCache.java @@ -37,7 +37,7 @@ { for (k2 = this.chunkZ; k2 <= i2; ++k2) { - chunk = par1World.getChunkFromChunkCoords(j2, k2); + chunk = par1World.getChunkIfExists(j2, k2); if (chunk != null) { diff --git a/src/main/java/net/minecraft/world/ChunkCoordIntPair.java b/src/main/java/net/minecraft/world/ChunkCoordIntPair.java index 8e457ff..1f804ca 100644 --- a/src/main/java/net/minecraft/world/ChunkCoordIntPair.java +++ b/src/main/java/net/minecraft/world/ChunkCoordIntPair.java @@ -1,5 +1,7 @@ package net.minecraft.world; +import org.ultramine.server.chunk.ChunkHash; + public class ChunkCoordIntPair { public final int chunkXPos; @@ -19,10 +21,7 @@ public int hashCode() { - long i = chunkXZ2Int(this.chunkXPos, this.chunkZPos); - int j = (int)i; - int k = (int)(i >> 32); - return j ^ k; + return ChunkHash.chunkToKey(chunkXPos, chunkZPos); } public boolean equals(Object par1Obj) diff --git a/src/main/java/net/minecraft/world/SpawnerAnimals.java b/src/main/java/net/minecraft/world/SpawnerAnimals.java index da396fb..d26b751 100644 --- a/src/main/java/net/minecraft/world/SpawnerAnimals.java +++ b/src/main/java/net/minecraft/world/SpawnerAnimals.java @@ -64,7 +64,7 @@ int cx = l + j; int cz = i1 + k; - if(par1WorldServer.chunkExists(cx, cz)) + if(par1WorldServer.chunkRoundExists(cx, cz, 1)) { boolean flag3 = l == -b0 || l == b0 || i1 == -b0 || i1 == b0; ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(cx, cz); diff --git a/src/main/java/net/minecraft/world/World.java b/src/main/java/net/minecraft/world/World.java index b9632b2..de35ada 100644 --- a/src/main/java/net/minecraft/world/World.java +++ b/src/main/java/net/minecraft/world/World.java @@ -31,6 +31,7 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.init.Blocks; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; @@ -1996,7 +1997,7 @@ //boolean isForced = getPersistentChunks().containsKey(new ChunkCoordIntPair(i >> 4, j >> 4)); //byte b0 = isForced ? (byte)0 : 32; //boolean canUpdate = !par2 || this.checkChunksExist(i - b0, 0, j - b0, i + b0, 0, j + b0); - boolean canUpdate = par1Entity.isEntityPlayerMP() || activeChunkSet.containsKey(ChunkHash.chunkToKey(i >> 4, j >> 4)) && this.chunkRoundExists(i >> 4, j >> 4, 3); + boolean canUpdate = par1Entity.isEntityPlayerMP() || activeChunkSet.containsKey(ChunkHash.chunkToKey(i >> 4, j >> 4)); //if (!canUpdate) //{ @@ -2026,6 +2027,11 @@ par1Entity.onUpdate(); } } + else if(par1Entity.isEntityPlayerMP()) + { + ((EntityPlayerMP)par1Entity).getChunkMgr().updatePlayerPertinentChunks(); + ((EntityPlayerMP)par1Entity).getChunkMgr().update(); + } this.theProfiler.startSection("chunkCheck"); @@ -2739,14 +2745,13 @@ k = MathHelper.floor_double(entityplayer.posZ / 16.0D); int b0 = getChunkUpdateRadius(); - activeChunkSet.put(ChunkHash.chunkToKey(j, k), (byte)0); for (int l = -b0; l <= b0; ++l) { for (int i1 = -b0; i1 <= b0; ++i1) { int cx = l + j; int cz = i1 + k; - if(chunkExists(cx, cz) && chunkExists(cx-1, cz) && chunkExists(cx, cz-1) && chunkExists(cx+1, cz) && chunkExists(cx, cz+1)) + if(chunkRoundExists(cx, cz, 1)) { int key = ChunkHash.chunkToKey(cx, cz); int priority = Math.max(Math.abs(l), Math.abs(i1)); @@ -3980,10 +3985,22 @@ public static final int MAX_BLOCK_COORD = 500000;//524288; + public Chunk getChunkIfExists(int cx, int cz) + { + return getChunkFromChunkCoords(cx, cz); + } + + public Block getBlockIfExists(int x, int y, int z) + { + if(blockExists(x, y, z)) + return getBlock(x, y, z); + return Blocks.air; + } + public boolean chunkRoundExists(int cx, int cz, int radius) { - for(int x = cx - radius; x < cx + radius; x++) - for(int z = cz - radius; z < cz + radius; z++) + for(int x = cx - radius; x <= cx + radius; x++) + for(int z = cz - radius; z <= cz + radius; z++) if(!chunkExists(x, z)) return false; return true; } diff --git a/src/main/java/net/minecraft/world/WorldServer.java b/src/main/java/net/minecraft/world/WorldServer.java index e91b6d7..cd47fd3 100644 --- a/src/main/java/net/minecraft/world/WorldServer.java +++ b/src/main/java/net/minecraft/world/WorldServer.java @@ -336,6 +336,7 @@ this.theProfiler.startSection("getChunk"); Chunk chunk = this.getChunkFromChunkCoords(chunkX, chunkZ); + chunk.setActive(); this.theProfiler.startSection("updatePending"); this.updatePendingOf(chunk); this.func_147467_a(k, l, chunk); diff --git a/src/main/java/net/minecraft/world/chunk/Chunk.java b/src/main/java/net/minecraft/world/chunk/Chunk.java index 615d022..7fcf5ab 100644 --- a/src/main/java/net/minecraft/world/chunk/Chunk.java +++ b/src/main/java/net/minecraft/world/chunk/Chunk.java @@ -33,6 +33,7 @@ import net.minecraft.world.ChunkPosition; import net.minecraft.world.EnumSkyBlock; import net.minecraft.world.World; +import net.minecraft.world.WorldServer; import net.minecraft.world.biome.BiomeGenBase; import net.minecraft.world.biome.WorldChunkManager; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; @@ -42,6 +43,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.ultramine.server.chunk.ChunkBindState; import org.ultramine.server.chunk.ChunkHash; import org.ultramine.server.chunk.PendingBlockUpdate; @@ -925,6 +927,9 @@ this.worldObj.addLoadedEntities(this.entityLists[i]); } MinecraftForge.EVENT_BUS.post(new ChunkEvent.Load(this)); + + loadTime = unbindTime = ((WorldServer)worldObj).func_73046_m().getTickCounter(); + lastsavePendingCount = pendingUpdatesSet == null ? 0 : pendingUpdatesSet.size(); } public void onChunkUnload() @@ -1014,12 +1019,9 @@ { if (par1) { - if (this.hasEntities && this.worldObj.getTotalWorldTime() != this.lastSaveTime || this.isModified) - { - return true; - } + return shouldSaveOnUnload(); } - else if (this.hasEntities && this.worldObj.getTotalWorldTime() >= this.lastSaveTime + 600L) + else if (wasActive && this.hasEntities && this.worldObj.getTotalWorldTime() >= this.lastSaveTime + 600L) { return true; } @@ -1537,6 +1539,12 @@ private Set pendingUpdatesSet; private TreeSet pendingUpdatesQueue; + private ChunkBindState bindState = ChunkBindState.NONE; + private int loadTime; + private int unbindTime; + private boolean wasActive; + private int lastsavePendingCount; + public PendingBlockUpdate pollPending(long time) { if(pendingUpdatesQueue == null || pendingUpdatesQueue.size() == 0) return null; @@ -1578,4 +1586,52 @@ { return pendingUpdatesQueue; } + + public ChunkBindState getBindState() + { + return bindState; + } + + public void setBindState(ChunkBindState bindState) + { + this.bindState = bindState; + } + + public void unbind() + { + if(bindState.canChangeState()) + bindState = ChunkBindState.NONE; + updateUnbindTime(); + } + + public void updateUnbindTime() + { + unbindTime = ((WorldServer)worldObj).func_73046_m().getTickCounter(); + } + + public int getLoadTime() + { + return loadTime; + } + + public int getUnbindTime() + { + return unbindTime; + } + + public void setActive() + { + wasActive = true; + } + + public void postSave() + { + wasActive = false; + lastsavePendingCount = pendingUpdatesSet == null ? 0 : pendingUpdatesSet.size(); + } + + public boolean shouldSaveOnUnload() + { + return isModified || pendingUpdatesSet != null && lastsavePendingCount != pendingUpdatesSet.size() || wasActive && hasEntities; + } } diff --git a/src/main/java/net/minecraft/world/chunk/storage/AnvilChunkLoader.java b/src/main/java/net/minecraft/world/chunk/storage/AnvilChunkLoader.java index 913ceab..95c979f 100644 --- a/src/main/java/net/minecraft/world/chunk/storage/AnvilChunkLoader.java +++ b/src/main/java/net/minecraft/world/chunk/storage/AnvilChunkLoader.java @@ -254,10 +254,11 @@ public void saveExtraData() { - while (this.writeNextIO()) - { - ; - } + //Async only +// while (this.writeNextIO()) +// { +// ; +// } } private void writeChunkToNBT(Chunk par1Chunk, World par2World, NBTTagCompound par3NBTTagCompound) @@ -502,6 +503,14 @@ } } + public int getSaveQueueSize() + { + synchronized(syncLockObject) + { + return pendingSaves.size(); + } + } + static class PendingChunk { public final ChunkCoordIntPair chunkCoordinate; diff --git a/src/main/java/net/minecraft/world/gen/ChunkProviderServer.java b/src/main/java/net/minecraft/world/gen/ChunkProviderServer.java index f2a149c..b06cc19 100644 --- a/src/main/java/net/minecraft/world/gen/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/world/gen/ChunkProviderServer.java @@ -13,7 +13,10 @@ import java.util.List; import java.util.Set; +import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.registry.GameRegistry; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReportCategory; import net.minecraft.entity.EnumCreatureType; @@ -37,6 +40,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.ultramine.server.chunk.ChunkBindState; +import org.ultramine.server.chunk.ChunkGC; import org.ultramine.server.chunk.ChunkHash; import org.ultramine.server.chunk.ChunkMap; import org.ultramine.server.chunk.IChunkLoadCallback; @@ -59,6 +64,9 @@ this.worldObj = par1WorldServer; this.currentChunkLoader = par2IChunkLoader; this.currentChunkProvider = par3IChunkProvider; + + if(isServer) + chunkGC = new ChunkGC(this); } public boolean chunkExists(int par1, int par2) @@ -68,22 +76,7 @@ public void unloadChunksIfNotNearSpawn(int par1, int par2) { - if (this.worldObj.provider.canRespawnHere() && DimensionManager.shouldLoadSpawn(this.worldObj.provider.dimensionId)) - { - ChunkCoordinates chunkcoordinates = this.worldObj.getSpawnPoint(); - int k = par1 * 16 + 8 - chunkcoordinates.posX; - int l = par2 * 16 + 8 - chunkcoordinates.posZ; - short short1 = 128; - - if (k < -short1 || k > short1 || l < -short1 || l > short1) - { - this.chunksToUnload.add(ChunkHash.chunkToKey(par1, par2)); - } - } - else - { - this.chunksToUnload.add(ChunkHash.chunkToKey(par1, par2)); - } + this.chunksToUnload.add(ChunkHash.chunkToKey(par1, par2)); } public void unloadAllChunks() @@ -124,6 +117,8 @@ else { chunk = ChunkIOExecutor.syncChunkLoad(this.worldObj, loader, this, par1, par2); + chunk.setBindState(ChunkBindState.LEAK); + logger.warn("The chunk("+par1+", "+par2+") was loaded sync", new Throwable()); } } else if (chunk == null) @@ -292,6 +287,7 @@ { this.safeSaveChunk(chunk); chunk.isModified = false; + chunk.postSave(); ++i; if (i == 24 && !par1) @@ -314,12 +310,10 @@ public boolean unloadQueuedChunks() { - if (!this.worldObj.levelSaving) +// if (!this.worldObj.levelSaving) { - for (ChunkCoordIntPair forced : this.worldObj.getPersistentChunks().keySet()) - { - this.chunksToUnload.remove(ChunkHash.chunkToKey(forced.chunkXPos, forced.chunkZPos)); - } + if(isServer) + chunkGC.onTick(); /* for (int i = 0; i < 100; ++i) @@ -343,31 +337,27 @@ } */ - int processed = 0; - for(TIntIterator it = chunksToUnload.iterator(); it.hasNext();) + Set persistentChunks = worldObj.getPersistentChunks().keySet(); + int savequeueSize = ((AnvilChunkLoader)currentChunkLoader).getSaveQueueSize(); + + for(TIntIterator it = chunksToUnload.iterator(); it.hasNext() && savequeueSize < MAX_SAVE_QUEUE_SIZE;) { - if(processed >= 20) break; int hash = it.next(); Chunk chunk = loadedChunkHashMap.get(hash); if(chunk != null) { - if(true/*chunk.getBindReason().canUnload()*/) + if(chunk.getBindState().canUnload() && !persistentChunks.contains(chunk.getChunkCoordIntPair())) { chunk.onChunkUnload(); - if(true/*chunk.shouldSaveOnUnload()*/) + if(chunk.shouldSaveOnUnload()) { - processed++; + savequeueSize++; safeSaveChunk(chunk); } this.safeSaveExtraChunkData(chunk); this.loadedChunkHashMap.remove(hash); - //chunk.postChunkUnload(); } } - else - { - logger.warn("Not existing chunk was queued for unload (" + ChunkHash.keyToX(hash) + ", " + ChunkHash.keyToZ(hash) + ")"); - } it.remove(); } @@ -411,6 +401,12 @@ /* ======================================== ULTRAMINE START =====================================*/ + private static final int MAX_SAVE_QUEUE_SIZE = 20; + private static final boolean isServer = FMLCommonHandler.instance().getSide().isServer(); + + @SideOnly(Side.SERVER) + private ChunkGC chunkGC; + public void loadAsync(int x, int z, IChunkLoadCallback callback) { Chunk chunk = loadedChunkHashMap.get(x, z); @@ -425,8 +421,29 @@ } } + public void loadAsyncRadius(int cx, int cz, int radius, IChunkLoadCallback callback) + { + for(int x = cx - radius; x <= cx + radius; x++) + for(int z = cz - radius; z <= cz + radius; z++) + loadAsync(x, z, callback); + } + public Chunk getChunkIfExists(int cx, int cz) { return loadedChunkHashMap.get(cx, cz); } + + public void unbindChunk(int cx, int cz) + { + Chunk chunk = loadedChunkHashMap.get(cx, cz); + if(chunk != null) + unbindChunk(chunk); + } + + public void unbindChunk(Chunk chunk) + { + chunk.unbind(); + if(!isServer) + unloadChunksIfNotNearSpawn(chunk.xPosition, chunk.zPosition); + } } \ No newline at end of file diff --git a/src/main/java/org/ultramine/server/WorldsConfig.java b/src/main/java/org/ultramine/server/WorldsConfig.java index 6d89dc9..6345771 100644 --- a/src/main/java/org/ultramine/server/WorldsConfig.java +++ b/src/main/java/org/ultramine/server/WorldsConfig.java @@ -55,6 +55,7 @@ { public int viewDistance = 10; public int chunkUpdateRadius = 7; + public int chunkCacheSize; public boolean enableChunkLoaders = true; } } diff --git a/src/main/java/org/ultramine/server/chunk/ChunkBindState.java b/src/main/java/org/ultramine/server/chunk/ChunkBindState.java new file mode 100644 index 0000000..780b086 --- /dev/null +++ b/src/main/java/org/ultramine/server/chunk/ChunkBindState.java @@ -0,0 +1,49 @@ +package org.ultramine.server.chunk; + +public enum ChunkBindState +{ + /** + * Чанк ничем не занят и может быть выгружен в любой момент. Стандартное + * значение при асинхронной загрузке чанка. + */ + NONE, + /** + * Чанк занят игроком/игроками. Как только все игроки выйдут из радиуса + * прогрузки, значение сменится на NONE. + */ + PLAYER, + /** + * Чанк был загружен синхронно, в обход менеджера загрузки чанков. По логике + * ванильного майна это является утечкой памяти - чанк не будет выгружен из + * памяти до тех пор, пока не будет помечен к отгрузке вручную (например, в + * него зайдет и выйдет игрок). Но у нас чанк будет выгружен через некоторое + * время. + */ + LEAK, + /** + * Чанк обнаружен в списке PersistentChunks, созданного форжей для всяких + * чанклоадеров. Не будет выгружен до тех пор, пока не исчезнет из этого + * списка. + */ + FORGE, + /** + * Чанку запрещено выгружаться или изменять состояние бинда. Чанк не будет + * выгружен никогда. + */ + ETERNAL; + + public boolean canUnload() + { + return this == NONE; + } + + public boolean canChangeState() + { + return this != ETERNAL && this != FORGE; + } + + public boolean isLeak() + { + return this == LEAK || this == FORGE; + } +} diff --git a/src/main/java/org/ultramine/server/chunk/ChunkGC.java b/src/main/java/org/ultramine/server/chunk/ChunkGC.java new file mode 100644 index 0000000..4ff232f --- /dev/null +++ b/src/main/java/org/ultramine/server/chunk/ChunkGC.java @@ -0,0 +1,124 @@ +package org.ultramine.server.chunk; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.world.ChunkCoordIntPair; +import net.minecraft.world.WorldServer; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.gen.ChunkProviderServer; + +@SideOnly(Side.SERVER) +public class ChunkGC +{ + private static final int MIN_GC_INTERVAL = 100; + private static final int MAX_UNLOAD_QUEUE_SIZE = 128; + private static final int MAX_CHUNKS_PER_OP = 1024; + private static final int _10_MINUTES = 20*60*10; + + private final ChunkProviderServer provider; + private final WorldServer world; + + private int lastGCTime; + private int lastChunkCount; + private int minChunkDiff; + + public ChunkGC(ChunkProviderServer provider) + { + this.provider = provider; + this.world = provider.worldObj; + } + + public void onTick() + { + int confCacheSize = world.getConfig().chunkLoading.chunkCacheSize; + int chunksPerPlayer = (int)Math.pow(world.getConfig().chunkLoading.viewDistance*2 + 1, 2); + int boundChunks = world.playerEntities.size()*chunksPerPlayer + world.getPersistentChunks().size(); + int chunkLimit = boundChunks + confCacheSize + MAX_CHUNKS_PER_OP; + + int curTime = world.func_73046_m().getTickCounter(); + int unloadQueueSize = provider.chunksToUnload.size(); + int chunkCount = provider.loadedChunkHashMap.size() - unloadQueueSize; + int timePassed = curTime - lastGCTime; + int chunkDiff = chunkCount - lastChunkCount; + + if(chunkCount > chunkLimit && timePassed > MIN_GC_INTERVAL && unloadQueueSize < MAX_UNLOAD_QUEUE_SIZE && (minChunkDiff == 0 || chunkDiff > minChunkDiff)) + { + Set persistentChunks = world.getPersistentChunks().keySet(); + Collection all = provider.loadedChunkHashMap.valueCollection(); + List unbound = new ArrayList(all.size() - boundChunks); + for(Chunk chunk : all) + { + ChunkBindState state = chunk.getBindState(); + if(state.canUnload()) + { + unbound.add(chunk); + } + else if(state.isLeak() && curTime - chunk.getUnbindTime() > _10_MINUTES) + { + if(persistentChunks.contains(chunk.getChunkCoordIntPair())) + { + if(state != ChunkBindState.FORGE) + chunk.setBindState(ChunkBindState.FORGE); + chunk.updateUnbindTime(); + } + else + { + chunk.unbind(); + unbound.add(chunk); + } + } + } + + int unboundLimit = confCacheSize + MAX_CHUNKS_PER_OP + unloadQueueSize; + + if(unbound.size() > unboundLimit) + { + //performing GC + Collections.sort(unbound, new ChunkComparator(curTime)); + + for(int i = 0, s = Math.min(unbound.size(), MAX_CHUNKS_PER_OP); i < s; i++) + { + Chunk chunk = unbound.get(i); + provider.chunksToUnload.add(ChunkHash.chunkToKey(chunk.xPosition, chunk.zPosition)); + } + + if(unbound.size() - Math.min(unbound.size(), MAX_CHUNKS_PER_OP) > unboundLimit) + minChunkDiff = 0; + else + minChunkDiff = unboundLimit - unbound.size(); + } + else + { + minChunkDiff = unboundLimit - unbound.size(); + } + + lastGCTime = curTime; + lastChunkCount = provider.loadedChunkHashMap.size() - provider.chunksToUnload.size(); + } + } + + private static class ChunkComparator implements Comparator + { + private int curTime; + + public ChunkComparator(int curTime) + { + this.curTime = curTime; + } + + @Override + public int compare(Chunk c1, Chunk c2) + { + float c = (float)Math.max(curTime - c1.getLoadTime(), _10_MINUTES)/(float)Math.max(curTime - c1.getUnbindTime(), 1) + - (float)Math.max(curTime - c2.getLoadTime(), _10_MINUTES)/(float)Math.max(curTime - c2.getUnbindTime(), 1); + return c == 0 ? 0 : c < 0 ? -1 : 1; + } + }; +} diff --git a/src/main/java/org/ultramine/server/chunk/ChunkSendManager.java b/src/main/java/org/ultramine/server/chunk/ChunkSendManager.java index e64b310..da0c716 100644 --- a/src/main/java/org/ultramine/server/chunk/ChunkSendManager.java +++ b/src/main/java/org/ultramine/server/chunk/ChunkSendManager.java @@ -248,7 +248,7 @@ PlayerManager.PlayerInstance pi = manager.getOrCreateChunkWatcher(chunk.xPosition, chunk.zPosition, false); if (pi == null) - ((WorldServer)chunk.worldObj).theChunkProviderServer.unloadChunksIfNotNearSpawn(chunk.xPosition, chunk.zPosition); + ((WorldServer)chunk.worldObj).theChunkProviderServer.unbindChunk(chunk); } } @@ -258,7 +258,7 @@ PlayerManager.PlayerInstance pi = manager.getOrCreateChunkWatcher(chunk.xPosition, chunk.zPosition, false); if (pi == null) - ((WorldServer)chunk.worldObj).theChunkProviderServer.unloadChunksIfNotNearSpawn(chunk.xPosition, chunk.zPosition); + ((WorldServer)chunk.worldObj).theChunkProviderServer.unbindChunk(chunk); } } @@ -360,6 +360,7 @@ @Override public void onChunkLoaded(Chunk chunk) { + chunk.setBindState(ChunkBindState.PLAYER); executor.execute(new CompressAndSendChunkTask(chunk)); } }; diff --git a/src/main/java/org/ultramine/server/chunk/IChunkLoadCallback.java b/src/main/java/org/ultramine/server/chunk/IChunkLoadCallback.java index a4ca323..f31e4ca 100644 --- a/src/main/java/org/ultramine/server/chunk/IChunkLoadCallback.java +++ b/src/main/java/org/ultramine/server/chunk/IChunkLoadCallback.java @@ -4,5 +4,7 @@ public interface IChunkLoadCallback { + public static final IChunkLoadCallback EMPTY = new IChunkLoadCallback(){public void onChunkLoaded(Chunk chunk){}}; + public void onChunkLoaded(Chunk chunk); } diff --git a/src/main/resources/org/ultramine/defaults/defaultworlds.yml b/src/main/resources/org/ultramine/defaults/defaultworlds.yml index 9d40e1f..39ba8ea 100644 --- a/src/main/resources/org/ultramine/defaults/defaultworlds.yml +++ b/src/main/resources/org/ultramine/defaults/defaultworlds.yml @@ -16,10 +16,12 @@ pvp: true time: NORMAL weather: NORMAL - chunkLoading: + chunkLoading: &global_cl viewDistance: 10 chunkUpdateRadius: 7 + chunkCacheSize: 1024 enableChunkLoaders: true + worlds: world: <<: *global @@ -27,6 +29,9 @@ generation: <<: *global_gen providerID: 0 + chunkLoading: + <<: *global_cl + chunkCacheSize: 4096 world_nether: <<: *global dimension: -1