Package nallar.patched.storage

Source Code of nallar.patched.storage.ThreadedChunkProvider

package nallar.patched.storage;

import com.google.common.collect.ImmutableSetMultimap;
import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.common.registry.GameRegistry;
import nallar.collections.ConcurrentQueueSet;
import nallar.collections.SynchronizedList;
import nallar.patched.annotation.FakeExtend;
import nallar.tickthreading.Log;
import nallar.tickthreading.minecraft.ChunkGarbageCollector;
import nallar.tickthreading.minecraft.DeadLockDetector;
import nallar.tickthreading.minecraft.TickThreading;
import nallar.tickthreading.patcher.Declare;
import nallar.tickthreading.util.BooleanThreadLocalDefaultFalse;
import nallar.tickthreading.util.CounterThreadLocalAssumeZero;
import nallar.tickthreading.util.DoNothingRunnable;
import nallar.tickthreading.util.ServerThreadFactory;
import nallar.tickthreading.util.concurrent.NativeMutex;
import nallar.unsafe.UnsafeUtil;
import net.minecraft.entity.EnumCreatureType;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.management.PlayerManager;
import net.minecraft.util.IProgressUpdate;
import net.minecraft.util.LongHashMap;
import net.minecraft.world.ChunkCoordIntPair;
import net.minecraft.world.ChunkPosition;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.EmptyChunk;
import net.minecraft.world.chunk.IChunkProvider;
import net.minecraft.world.chunk.storage.AnvilChunkLoader;
import net.minecraft.world.chunk.storage.IChunkLoader;
import net.minecraft.world.gen.ChunkProviderServer;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.ForgeChunkManager;
import org.cliffc.high_scale_lib.NonBlockingHashMapLong;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.logging.*;

/**
* This is a replacement for ChunkProviderServer
* Instead of attempting to patch a class with many different implementations,
* this replaces it with an implementation which is intended to be compatible
* with both Forge and MCPC+.
*/
@FakeExtend
public abstract class ThreadedChunkProvider extends ChunkProviderServer implements IChunkProvider {
  /**
   * You may also use a synchronized block on generateLock,
   * and are encouraged to unless tryLock() is required.
   * This works as NativeMutex uses JVM monitors internally.
   */
  public final NativeMutex generateLock = new NativeMutex();
  public final NativeMutex populationLock = new NativeMutex();
  private static final ThreadPoolExecutor chunkLoadThreadPool;
  private final IChunkProvider generator; // Mojang shouldn't use the same interface for  :(

  static {
    chunkLoadThreadPool = new ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new ServerThreadFactory("Async ChunkLoader"));
    chunkLoadThreadPool.allowCoreThreadTimeOut(true);
  }

  private static final Runnable doNothingRunnable = new DoNothingRunnable();
  private static final int populationRange = 1;
  private final CounterThreadLocalAssumeZero populationCounter = new CounterThreadLocalAssumeZero();
  private final NonBlockingHashMapLong<AtomicInteger> chunkLoadLocks = new NonBlockingHashMapLong<AtomicInteger>();
  private final LongHashMap chunks = new LongHashMap();
  private final LongHashMap loadingChunks = new LongHashMap();
  private final LongHashMap unloadingChunks = new LongHashMap();
  private final ConcurrentQueueSet<Long> unloadStage0 = new ConcurrentQueueSet<Long>();
  private final ConcurrentLinkedQueue<QueuedUnload> unloadStage1 = new ConcurrentLinkedQueue<QueuedUnload>();
  private final IChunkLoader loader;
  private final WorldServer world;
  private final Chunk defaultEmptyChunk;
  private final BooleanThreadLocalDefaultFalse inUnload = new BooleanThreadLocalDefaultFalse();
  private final BooleanThreadLocalDefaultFalse worldGenInProgress;
  private boolean loadChunksInProvideChunk = true;
  private int loadChunksInProvideChunkTicks = 0;
  private int overloadCount = 0;
  private int saveTicks = 0;
  private int maxChunksToSave = 192;
  private int lastChunksSaved = -1;
  private Chunk lastChunk;
  // Mojang compatiblity fields.
  public final IChunkProvider currentChunkProvider;
  public Set<Long> chunksToUnload;
  public final List<Chunk> loadedChunks;
  public final IChunkLoader currentChunkLoader;
  public final LongHashMap loadedChunkHashMap = chunks;
  @SuppressWarnings("UnusedDeclaration")
  public boolean loadChunkOnProvideRequest;

  public ThreadedChunkProvider(WorldServer world, IChunkLoader loader, IChunkProvider generator) {
    super(world, loader, generator); // This call will be removed by javassist.
    currentChunkProvider = this.generator = generator;
    this.world = world;
    currentChunkLoader = this.loader = loader;
    loadedChunks = new SynchronizedList<Chunk>();
    world.emptyChunk = defaultEmptyChunk = new EmptyChunk(world, 0, 0);
    world.worldGenInProgress = worldGenInProgress = new BooleanThreadLocalDefaultFalse();
    world.inImmediateBlockUpdate = new BooleanThreadLocalDefaultFalse();
  }

  @Declare
  public static void onChunkLoad(Chunk chunk, Runnable runnable) {
    if (runnable instanceof nallar.tickthreading.util.ChunkLoadRunnable) {
      ((nallar.tickthreading.util.ChunkLoadRunnable) runnable).onLoad(chunk);
    } else {
      runnable.run();
    }
  }

  @Override
  @Declare
  public WorldServer getWorld() {
    return world;
  }

  @Override
  @Declare
  public List<Chunk> getLoadedChunks() {
    return loadedChunks;
  }

  @Override
  public boolean unloadQueuedChunks() {
    return generator.unloadQueuedChunks();
  }

  @SuppressWarnings({"ConstantConditions", "FieldRepeatedlyAccessedInMethod"})
  @Override
  @Declare
  public void tick() {
    int ticks = world.tickCount;
    // Handle unload requests
    final ConcurrentQueueSet<Long> unloadStage0 = this.unloadStage0;
    if (!unloadStage0.isEmpty()) {
      ImmutableSetMultimap<ChunkCoordIntPair, ForgeChunkManager.Ticket> persistentChunks = world.getPersistentChunks();
      PlayerManager playerManager = world.getPlayerManager();
      ChunkCoordIntPair chunkCoordIntPair = new ChunkCoordIntPair(0, 0);
      Long key_;
      int done = 0;
      while (++done != 75 && (key_ = unloadStage0.take()) != null) {
        long key = key_;
        int x = (int) key;
        int z = (int) (key >> 32);
        chunkCoordIntPair.chunkXPos = x;
        chunkCoordIntPair.chunkZPos = z;
        Chunk chunk = (Chunk) chunks.getValueByKey(key);
        if (chunk == null) {
          continue;
        }
        synchronized (chunk) {
          if (chunk.partiallyUnloaded || !chunk.queuedUnload || unloadingChunks.containsItem(key)) {
            continue;
          }
          if (persistentChunks.containsKey(chunkCoordIntPair) || playerManager.getOrCreateChunkWatcher(x, z, false) != null || !fireBukkitUnloadEvent(chunk)) {
            chunk.queuedUnload = false;
            continue;
          }
          if (lastChunk == chunk) {
            lastChunk = null;
          }
          chunk.partiallyUnloaded = true;
          chunk.onChunkUnloadTT();
          chunk.pendingBlockUpdates = world.getPendingBlockUpdates(chunk, false);
          loadedChunks.remove(chunk);
          chunks.remove(key);
          synchronized (unloadingChunks) {
            unloadingChunks.put(key, chunk);
            unloadStage1.add(new QueuedUnload(key, ticks));
          }
        }
      }

      if (loader != null) {
        loader.chunkTick();
      }
    }

    handleUnloadQueue(ticks - 3);

    if (ticks > 1200 && world.getDimension() == world.provider.dimensionId && world.provider.dimensionId != 0 && TickThreading.instance.allowWorldUnloading
        && loadedChunks.isEmpty() && ForgeChunkManager.getPersistentChunksFor(world).isEmpty()
        && (!TickThreading.instance.shouldLoadSpawn || !DimensionManager.shouldLoadSpawn(world.getDimension()))) {
      DimensionManager.unloadWorld(world.getDimension());
    }

    if (ticks % TickThreading.instance.chunkGCInterval == 0) {
      ChunkGarbageCollector.garbageCollect(world);
    }

    if (loadChunksInProvideChunkTicks++ == 200) {
      loadChunksInProvideChunk = false;
      int loaded = 0;
      int possible = world.getPersistentChunks().size();
      for (ChunkCoordIntPair chunkCoordIntPair : world.getPersistentChunks().keySet()) {
        if (getChunkAt(chunkCoordIntPair.chunkXPos, chunkCoordIntPair.chunkZPos, doNothingRunnable) == null) {
          loaded++;
        }
        possible++;
      }
      if (loaded > 0) {
        Log.info("Loaded " + loaded + '/' + possible + " persistent chunks in " + Log.name(world));
      }
    }
  }

  private void handleUnloadQueue(long queueThreshold) {
    handleUnloadQueue(queueThreshold, false);
  }

  private synchronized void handleUnloadQueue(long queueThreshold, boolean full) {
    int done = 0;
    // Handle unloading stage 1
    {
      QueuedUnload queuedUnload;
      while ((queuedUnload = unloadStage1.peek()) != null && queuedUnload.ticks <= queueThreshold && (full || ++done <= 200)) {
        long key = queuedUnload.key;
        synchronized (unloadingChunks) {
          if (!unloadStage1.remove(queuedUnload)) {
            continue;
          }
        }
        finalizeUnload(key);
      }
    }
  }

  private boolean finalizeUnload(long key) {
    Chunk chunk;
    synchronized (unloadingChunks) {
      // Don't remove here, as the chunk should be in the unloadingChunks map so that getChunkIfExists will still return it if used by a mod during chunk saving.
      chunk = (Chunk) unloadingChunks.getValueByKey(key);
    }
    if (chunk == null) {
      return false;
    }
    if (!chunk.partiallyUnloaded) {
      return false;
    }
    synchronized (chunk) {
      if (chunk.alreadySavedAfterUnload) {
        return false;
      }
      chunk.alreadySavedAfterUnload = true;
      try {
        boolean notInUnload = !inUnload.getAndSet(true);
        boolean notWorldGen = !worldGenInProgress.getAndSet(true);
        saveChunk(chunk);
        safeSaveExtraChunkData(chunk);
        if (notWorldGen) {
          worldGenInProgress.set(false);
        }
        if (notInUnload) {
          inUnload.set(false);
        }
        if (chunks.containsItem(key)) {
          Log.severe("Failed to unload chunk " + key + ", it was reloaded during unloading");
        }
      } finally {
        if (unloadingChunks.remove(key) != chunk) {
          Log.severe("While unloading " + chunk + " it was replaced/removed from the unloadingChunks map.");
        }
      }
    }

    return true;
  }

  // Public visibility as it will be accessed from net.minecraft.whatever, not actually this class
  // (Inner classes are not remapped in patching)
  public static class QueuedUnload {
    public final int ticks;
    public final long key;

    public QueuedUnload(long key, int ticks) {
      this.key = key;
      this.ticks = ticks;
    }
  }

  @Override
  @Declare
  public boolean safeToGenerate() {
    return worldGenInProgress.get() == Boolean.FALSE && !Thread.holdsLock(generateLock);
  }

  @Override
  public boolean chunkExists(int x, int z) {
    long key = key(x, z);
    return chunks.containsItem(key) || (worldGenInProgress.get() == Boolean.TRUE && (loadingChunks.containsItem(key) || (inUnload.get() == Boolean.TRUE && unloadingChunks.containsItem(key))));
  }

  @Override
  @Declare
  public boolean chunksExist(int minX, int minZ, int maxX, int maxZ) {
    boolean worldGenInProgress = this.worldGenInProgress.get() == Boolean.TRUE;
    boolean inUnload = worldGenInProgress && this.inUnload.get() == Boolean.TRUE;
    for (int x = minX; x <= maxX; ++x) {
      for (int z = minZ; z <= maxZ; ++z) {
        long key = key(x, z);
        if (!(chunks.containsItem(key) || (worldGenInProgress && (loadingChunks.containsItem(key) || (inUnload && unloadingChunks.containsItem(key)))))) {
          return false;
        }
      }
    }
    return true;
  }

  @Override
  @Declare
  public boolean unloadChunk(int x, int z) {
    if (world.getPersistentChunks().keySet().contains(new ChunkCoordIntPair(x, z))) {
      return false;
    }
    long hash = key(x, z);
    Chunk chunk = (Chunk) chunks.getValueByKey(hash);
    if (chunk == null) {
      return false;
    }
    chunk.queuedUnload = true;
    return unloadStage0.add(hash);
  }

  @Override
  @Declare
  public void unloadChunkForce(long hash) {
    if (unloadStage0.add(hash)) {
      Chunk chunk = (Chunk) chunks.getValueByKey(hash);
      if (chunk != null) {
        chunk.queuedUnload = true;
      }
    }
  }

  @Override
  public void unloadChunksIfNotNearSpawn(int x, int z) {
    unloadChunk(x, z);
  }

  @Override
  public void unloadAllChunks() {
    if (loadedChunks.size() > world.getPersistentChunks().size()) {
      synchronized (loadedChunks) {
        for (Chunk chunk : loadedChunks) {
          unloadStage0.add(key(chunk.xPosition, chunk.zPosition));
        }
      }
    }
  }

  @Deprecated
  public void unloadChunkImmediately(int x, int z, boolean save) {
    /** I lied, doesn't unload it immediately. **/
    unloadChunk(x, z);
  }

  @Deprecated
  public Chunk regenerateChunk(int x, int z) {
    long key = key(x, z);
    AtomicInteger lock = getLock(key);
    synchronized (lock) {
      try {
        Chunk chunk = getChunkIfExists(x, z);
        if (chunk != null) {
          finalizeUnload(key);
          do {
            chunk = getChunkIfExists(x, z);
            if (chunk == null) {
              continue;
            }
            chunk.queuedUnload = true;
            synchronized (chunk) {
              if (chunk.partiallyUnloaded || unloadingChunks.containsItem(key)) {
                continue;
              }
              if (!fireBukkitUnloadEvent(chunk)) {
                Log.warning("Bukkit cancelled chunk unload for regeneration unload of " + x + ", " + z, new Throwable());
              }
              if (lastChunk == chunk) {
                lastChunk = null;
              }
              chunk.partiallyUnloaded = true;
              chunk.onChunkUnloadTT();
              chunk.pendingBlockUpdates = world.getPendingBlockUpdates(chunk, false);
              loadedChunks.remove(chunk);
              chunks.remove(key);
              synchronized (unloadingChunks) {
                unloadingChunks.put(key, chunk);
                unloadStage1.add(new QueuedUnload(key, 0));
              }
            }
          } while (false);
          finalizeUnload(key);
        }
        return getChunkAtInternal(x, z, true, true);
      } finally {
        lock.decrementAndGet();
      }
    }
  }

  @Override
  @Declare
  public void cacheChunk(int x, int z) {
    if (!((AnvilChunkLoader) loader).isChunkCacheFull()) {
      chunkLoadThreadPool.execute(new ChunkCacheRunnable(this, x, z));
    }
  }

  @Override
  @Declare
  public void cacheChunkInternal(int x, int z) {
    AnvilChunkLoader anvilChunkLoader = (AnvilChunkLoader) loader;
    if (anvilChunkLoader.isChunkCacheFull()) {
      return;
    }
    long key = key(x, z);
    final AtomicInteger lock = getLock(key);
    try {
      synchronized (lock) {
        if (chunks.containsItem(key) || loadingChunks.containsItem(key) || unloadingChunks.containsItem(key)) {
          return;
        }
        anvilChunkLoader.cacheChunk(world, x, z);
      }
    } finally {
      if (lock.decrementAndGet() == 0) {
        loadingChunks.remove(key);
      }
    }
  }

  @Override
  public final Chunk provideChunk(int x, int z) {
    Chunk chunk = getChunkIfExists(x, z);

    if (chunk != null) {
      return chunk;
    }

    if (loadChunksInProvideChunk || populationCounter.getCount() != 0) {
      return getChunkAtInternal(x, z, true, false);
    }
    /* else {
      Log.warning("Didn't load a chunk at " + x + ',' + z, new Throwable());
    } */

    return defaultEmptyChunk;
  }

  @Override
  public final Chunk loadChunk(int x, int z) {
    Chunk chunk = getChunkAt(x, z, true, false, null);
    chunk.queuedUnload = false;
    return chunk;
  }

  @Override
  @Declare
  public final Chunk getChunkAt(final int x, final int z, final Runnable runnable) {
    return getChunkAt(x, z, true, false, runnable);
  }

  @Override
  @Declare
  public final Chunk getChunkAt(final int x, final int z, boolean allowGenerate, final Runnable runnable) {
    return getChunkAt(x, z, allowGenerate, false, runnable);
  }

  @Override
  @Declare
  public final Chunk getChunkIfExists(int x, int z) {
    Chunk chunk = lastChunk;
    if (chunk != null && chunk.xPosition == x && chunk.zPosition == z) {
      return chunk;
    }
    long key = key(x, z);
    chunk = (Chunk) chunks.getValueByKey(key);
    if (chunk == null && worldGenInProgress.get() == Boolean.TRUE) {
      chunk = (Chunk) loadingChunks.getValueByKey(key);
      if (chunk == null && inUnload.get() == Boolean.TRUE) {
        chunk = (Chunk) unloadingChunks.getValueByKey(key);
      }
    }
    if (chunk == null) {
      return null;
    }
    lastChunk = chunk;
    return chunk;
  }

  @Override
  @Declare
  public final Chunk getChunkAt(final int x, final int z, boolean allowGenerate, boolean regenerate, final Runnable runnable) {
    Chunk chunk = getChunkIfExists(x, z);

    if (chunk != null) {
      if (runnable != null) {
        ThreadedChunkProvider.onChunkLoad(chunk, runnable);
      }
      return chunk;
    }

    if (runnable != null) {
      if (world.unloaded) {
        throw new IllegalStateException("Trying to load chunks in an unloaded world.");
      }
      chunkLoadThreadPool.execute(new ChunkLoadRunnable(x, z, allowGenerate, regenerate, runnable, this));
      return null;
    }

    return getChunkAtInternal(x, z, allowGenerate, regenerate);
  }

  @SuppressWarnings("ConstantConditions")
  private Chunk getChunkAtInternal(final int x, final int z, boolean allowGenerate, boolean regenerate) {
    if (!safeToGenerate()) {
      return defaultEmptyChunk;
    }

    long key = key(x, z);
    Chunk chunk;

    final AtomicInteger lock = getLock(key);
    boolean wasGenerated = false;
    try {
      boolean inLoadingMap = false;

      // Lock on the lock for this chunk - prevent multiple instances of the same chunk
      synchronized (lock) {
        chunk = (Chunk) chunks.getValueByKey(key);
        if (chunk != null) {
          return chunk;
        }
        chunk = (Chunk) loadingChunks.getValueByKey(key);
        if (regenerate) {
          if (!allowGenerate) {
            throw new IllegalArgumentException();
          }
          loadingChunks.put(key, defaultEmptyChunk);
        } else if (chunk == null) {
          finalizeUnload(key);
          chunk = safeLoadChunk(x, z);
          if (chunk != null && (chunk.xPosition != x || chunk.zPosition != z)) {
            Log.severe("Chunk at " + chunk.xPosition + ',' + chunk.zPosition + " was stored at " + x + ',' + z + "\nResetting this chunk.");
            chunk = null;
          }
          if (chunk == null) {
            loadingChunks.put(key, defaultEmptyChunk);
            if (!allowGenerate) {
              return null;
            } else if (generator == null) {
              return defaultEmptyChunk;
            }
          } else {
            loadingChunks.put(key, chunk);
            inLoadingMap = true;
            if (!world.loadEventFired) {
              Log.warning("Loaded chunk before world load event fired, this can cause many issues, including loss of multiblock data.", new Throwable());
            }
          }
        } else if (chunk != defaultEmptyChunk) {
          inLoadingMap = true;
        }
      }
      // Unlock this chunk - avoids a deadlock
      // Thread A - requests chunk A - needs genned
      // Thread B - requests chunk B - needs genned
      // In thread A, redpower tries to load chunk B
      // because its marble gen is buggy.
      // Thread B is now waiting for the generate lock,
      // Thread A is waiting for the lock on chunk B

      // Lock the generation lock - ChunkProviderGenerate isn't threadsafe at all

      boolean locked = true;
      generateLock.lock();
      try {
        synchronized (lock) {
          chunk = (Chunk) chunks.getValueByKey(key);
          if (chunk != null) {
            return chunk;
          }
          worldGenInProgress.set(Boolean.TRUE);
          try {
            chunk = (Chunk) loadingChunks.getValueByKey(key);
            if (chunk == null) {
              Log.severe("Failed to load chunk " + chunk + " at " + x + ',' + z + " as it is missing from the loading chunks map.");
              return defaultEmptyChunk;
            }
            if (chunk == defaultEmptyChunk) {
              try {
                chunk = generator.provideChunk(x, z);
                if (chunk == null) {
                  Log.severe("Null chunk was generated for " + x + ',' + z + " by " + generator);
                  return defaultEmptyChunk;
                }
                chunk.isTerrainPopulated = false;
                wasGenerated = true;
              } catch (Throwable t) {
                Log.severe("Failed to generate a chunk in " + Log.name(world) + " at chunk coords " + x + ',' + z);
                throw UnsafeUtil.throwIgnoreChecked(t);
              }
            } else {
              if (generator != null) {
                generator.recreateStructures(x, z);
              }
            }

            if (!inLoadingMap) {
              loadingChunks.put(key, chunk);
            }

            locked = false;
            generateLock.unlock();
            chunk.threadUnsafeChunkLoad();

            chunks.put(key, chunk);
          } finally {
            worldGenInProgress.set(Boolean.FALSE);
          }
        }
      } finally {
        if (locked) {
          generateLock.unlock();
        }
      }
    } finally {
      if (lock.decrementAndGet() == 0) {
        loadingChunks.remove(key);
      }
    }

    loadedChunks.add(chunk);
    chunk.onChunkLoad();
    fireBukkitLoadEvent(chunk, wasGenerated);
    chunkLoadLocks.remove(key);
    tryPopulateChunks(chunk);

    return chunk;
  }

  private void populate(Chunk chunk) {
    synchronized (populationLock) {
      if (populationCounter.increment() > 25) {
        throw new Error("Recursive population depth limit exceeded: 25 chunks.");
      }
      try {
        synchronized (chunk) {
          int x = chunk.xPosition;
          int z = chunk.zPosition;
          if (chunk.isTerrainPopulated) {
            Log.warning("Attempted to populate chunk " + x + ',' + z + " which is already populated.");
            return;
          }
          if (generator != null) {
            generator.populate(this, x, z);
            fireBukkitPopulateEvent(chunk);
            GameRegistry.generateWorld(x, z, world, generator, this);
            chunk.setChunkModified(); // It may have been modified in generator.populate/GameRegistry.generateWorld.
          }
          //noinspection ConstantConditions
          if (chunk.isTerrainPopulated) {
            Log.warning("Chunk " + chunk + " had its isTerrainPopulated field set to true incorrectly by external code while populating");
          }
          chunk.isTerrainPopulated = true;
        }
      } finally {
        populationCounter.decrement();
      }
    }
  }

  private void tryPopulateChunks(Chunk centerChunk) {
    int cX = centerChunk.xPosition;
    int cZ = centerChunk.zPosition;
    for (int x = cX - populationRange; x <= cX + populationRange; x++) {
      for (int z = cZ - populationRange; z <= cZ + populationRange; z++) {
        Chunk chunk = getChunkFastUnsafe(x, z);
        if (chunk != null && !chunk.queuedUnload && !chunk.partiallyUnloaded && !chunk.isTerrainPopulated && checkChunksExistLoadedNormally(x - populationRange, x + populationRange, z - populationRange, z + populationRange)) {
          populate(chunk);
        }
      }
    }
  }

  public boolean checkChunksExistLoadedNormally(int minX, int maxX, int minZ, int maxZ) {
    for (int x = minX; x <= maxX; ++x) {
      for (int z = minZ; z <= maxZ; ++z) {
        Chunk chunk = getChunkFastUnsafe(x, z);
        if (chunk == null || chunk.queuedUnload || chunk.partiallyUnloaded) {
          return false;
        }
      }
    }

    return true;
  }

  @Override
  @Declare
  public Chunk getChunkFastUnsafe(int x, int z) {
    return (Chunk) chunks.getValueByKey(key(x, z));
  }

  private AtomicInteger getLock(long key) {
    AtomicInteger lock = chunkLoadLocks.get(key);
    if (lock != null) {
      lock.incrementAndGet();
      return lock;
    }
    AtomicInteger newLock = new AtomicInteger(1);
    lock = chunkLoadLocks.putIfAbsent(key, newLock);
    if (lock != null) {
      lock.incrementAndGet();
      return lock;
    }
    return newLock;
  }

  @Override
  @Declare
  public void fireBukkitLoadEvent(Chunk chunk, boolean newlyGenerated) {
  }

  @Override
  @Declare
  public boolean fireBukkitUnloadEvent(Chunk chunk) {
    return true;
  }

  @Override
  protected Chunk safeLoadChunk(int x, int z) {
    if (loader == null) {
      return null;
    }
    try {
      Chunk chunk = loader.loadChunk(world, x, z);

      if (chunk != null) {
        chunk.lastSaveTime = world.getTotalWorldTime();
      }

      return chunk;
    } catch (Exception e) {
      FMLLog.log(Level.SEVERE, e, "Failed to load chunk at " + x + ',' + z);
      return null;
    }
  }

  @Override
  protected void safeSaveExtraChunkData(Chunk chunk) {
    if (loader == null) {
      return;
    }
    try {
      loader.saveExtraChunkData(world, chunk);
    } catch (Exception e) {
      FMLLog.log(Level.SEVERE, e, "Failed to save extra chunk data for " + chunk);
    }
  }

  @Deprecated
  @Override
  protected void safeSaveChunk(Chunk chunk) {
    throw new Error("Not supported with TT");
  }

  @Override
  @Declare
  public void saveChunk(Chunk chunk) {
    if (loader == null) {
      return;
    }
    try {
      chunk.lastSaveTime = world.getTotalWorldTime();
      loader.saveChunk(world, chunk);
    } catch (Exception e) {
      FMLLog.log(Level.SEVERE, e, "Failed to save chunk " + chunk);
    }
  }

  @Deprecated
  @Override
  public void populate(IChunkProvider chunkProvider, int x, int z) {
    throw new UnsupportedOperationException("Unused, inefficient parameter choice.");
  }

  @Override
  @Declare
  public void fireBukkitPopulateEvent(Chunk chunk) {
  }

  @Override
  public boolean saveChunks(boolean fullSaveRequired, IProgressUpdate progressUpdate) {
    boolean saveAll = fullSaveRequired;
    if (saveTicks++ % 512 == 0) {
      int loadedChunks = chunks.getNumHashElements();
      if (loadedChunks > 1536) {
        DeadLockDetector.tickAhead(5);
        DeadLockDetector.sendChatSafely("Fully saving world " + world.getName() + " with " + loadedChunks + " chunks, expect a short lag spike.");
      } else {
        DeadLockDetector.tickAhead(1);
      }
      saveAll = true;
    }
    int savedChunks = 0;

    long worldTime = world.getTotalWorldTime();
    List<Chunk> chunksToSave = new ArrayList<Chunk>();
    boolean overload = false;
    boolean warnableOverload = false;
    int fullChunksToSave = 0;
    synchronized (loadedChunks) {
      for (Chunk chunk : loadedChunks) {
        if (!chunk.partiallyUnloaded && chunk.needsSaving(saveAll, worldTime)) {
          if (!overload) {
            if (++savedChunks == maxChunksToSave && !saveAll) {
              if (++overloadCount > 25) {
                warnableOverload = true;
              }
              overload = true;
            }
            chunk.isModified = false;
            chunksToSave.add(chunk);
          }
          fullChunksToSave++;
        }
      }
    }

    if (warnableOverload) {
      Log.warning("Partial save queue overloaded in " + Log.name(world) + ". You should probably decrease saveInterval to avoid lag spikes. Only saved " + (savedChunks - 1) + " out of " + fullChunksToSave);
      maxChunksToSave = (maxChunksToSave * 3) / 2;
      overloadCount -= 4;
    }

    for (Chunk chunk : chunksToSave) {
      if (chunk.partiallyUnloaded) {
        continue;
      }
      if (chunks.getValueByKey(key(chunk.xPosition, chunk.zPosition)) != chunk) {
        if (MinecraftServer.getServer().isServerRunning()) {
          Log.warning("Not saving " + chunk + ", not in correct location in chunks map.");
        }
        continue;
      }

      if (fullSaveRequired) {
        safeSaveExtraChunkData(chunk);
      }

      saveChunk(chunk);
      chunk.isModified = false; // Just in case a mod is managing to set the chunk as modified during saving, don't want pointless repeated saving.
    }

    lastChunksSaved = savedChunks;

    if (overloadCount > 0 && savedChunks != maxChunksToSave) {
      overloadCount--;
    }

    if (fullSaveRequired) {
      handleUnloadQueue(Long.MAX_VALUE, true);

      if (loader != null) {
        loader.saveExtraData();
      }
    }

    return true;
  }

  @Override
  public boolean canSave() {
    return !world.canNotSave;
  }

  @Override
  public String makeString() {
    return "Loaded " + loadedChunks.size() + " Loading " + loadingChunks.getNumHashElements() + " Unload " + unloadStage0.size() + " UnloadSave " + unloadStage1.size() + " Locks " + chunkLoadLocks.size() + " PartialSave " + lastChunksSaved + " Forced " + world.getPersistentChunks().size() + " Cached " + ((AnvilChunkLoader) loader).getCachedChunks();
  }

  @Override
  public List getPossibleCreatures(EnumCreatureType creatureType, int x, int y, int z) {
    return generator.getPossibleCreatures(creatureType, x, y, z);
  }

  @Override
  public ChunkPosition findClosestStructure(World world, String name, int x, int y, int z) {
    return generator.findClosestStructure(world, name, x, y, z);
  }

  @Override
  public int getLoadedChunkCount() {
    return loadedChunks.size();
  }

  @Override
  public void recreateStructures(int x, int z) {
  }

  @Override
  @Declare
  public net.minecraft.nbt.NBTTagCompound readChunkNBT(int x, int z) {
    NBTTagCompound nbtTagCompound = ((AnvilChunkLoader) loader).readChunkNBT(world, x, z, true);
    if (nbtTagCompound == null) {
      return null;
    }
    return nbtTagCompound.getCompoundTag("Level");
  }

  private static long key(int x, int z) {
    return (((long) z) << 32) | (x & 0xffffffffL);
  }

  public static class ChunkLoadRunnable implements Runnable {
    private final int x;
    private final int z;
    private final Runnable runnable;
    private final ChunkProviderServer provider;
    private final boolean allowGenerate;
    private final boolean regenerate;

    public ChunkLoadRunnable(int x, int z, boolean allowGenerate, boolean regenerate, Runnable runnable, ChunkProviderServer provider) {
      this.x = x;
      this.z = z;
      this.allowGenerate = allowGenerate;
      this.regenerate = regenerate;
      this.runnable = runnable;
      this.provider = provider;
    }

    @Override
    public void run() {
      try {
        WorldServer worldServer = provider.getWorld();
        if (worldServer.unloaded) {
          FMLLog.warning("Failed to load chunk at " + worldServer.getDimension() + ':' + x + ',' + z + " asynchronously. The world is no longer loaded.");
          return;
        }
        Chunk chunk = provider.getChunkAt(x, z, allowGenerate, regenerate, null);
        if (chunk == null || (allowGenerate && chunk instanceof EmptyChunk)) {
          FMLLog.warning("Failed to load chunk at " + Log.name(worldServer) + ':' + x + ',' + z + " asynchronously. Provided " + chunk);
        } else {
          ChunkProviderServer.onChunkLoad(chunk, runnable);
        }
      } catch (Throwable t) {
        FMLLog.log(Level.SEVERE, t, "Exception loading chunk asynchronously.");
      }
    }
  }

  public static class ChunkCacheRunnable implements Runnable {
    private final ChunkProviderServer chunkProviderServer;
    private final int x;
    private final int z;

    public ChunkCacheRunnable(final ChunkProviderServer chunkProviderServer, final int x, final int z) {
      this.chunkProviderServer = chunkProviderServer;
      this.x = x;
      this.z = z;
    }

    @Override
    public void run() {
      chunkProviderServer.cacheChunkInternal(x, z);
    }
  }
}
TOP

Related Classes of nallar.patched.storage.ThreadedChunkProvider

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.