Package net.glowstone

Source Code of net.glowstone.ChunkManager$ChunkLock

package net.glowstone;

import net.glowstone.constants.GlowBiome;
import net.glowstone.io.ChunkIoService;
import org.bukkit.block.Biome;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkPopulateEvent;
import org.bukkit.generator.BlockPopulator;
import org.bukkit.generator.ChunkGenerator;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;

/**
* A class which manages the {@link GlowChunk}s currently loaded in memory.
* @author Graham Edgecombe
*/
public final class ChunkManager {

    /**
     * The world this ChunkManager is managing.
     */
    private final GlowWorld world;

    /**
     * The chunk I/O service used to read chunks from the disk and write them to
     * the disk.
     */
    private final ChunkIoService service;

    /**
     * The chunk generator used to generate new chunks.
     */
    private final ChunkGenerator generator;

    /**
     * A map of chunks currently loaded in memory.
     */
    private final ConcurrentMap<GlowChunk.Key, GlowChunk> chunks = new ConcurrentHashMap<>();

    /**
     * A map of chunks which are being kept loaded by players or other factors.
     */
    private final ConcurrentMap<GlowChunk.Key, Set<ChunkLock>> locks = new ConcurrentHashMap<>();

    /**
     * Creates a new chunk manager with the specified I/O service and world
     * generator.
     * @param service The I/O service.
     * @param generator The world generator.
     */
    public ChunkManager(GlowWorld world, ChunkIoService service, ChunkGenerator generator) {
        this.world = world;
        this.service = service;
        this.generator = generator;
    }

    /**
     * Get the chunk generator.
     */
    public ChunkGenerator getGenerator() {
        return generator;
    }

    /**
     * Gets a chunk object representing the specified coordinates, which might
     * not yet be loaded.
     * @param x The X coordinate.
     * @param z The Z coordinate.
     * @return The chunk.
     */
    public GlowChunk getChunk(int x, int z) {
        GlowChunk.Key key = new GlowChunk.Key(x, z);
        if (chunks.containsKey(key)) {
            return chunks.get(key);
        } else {
            // only create chunk if it's not in the map already
            GlowChunk chunk = new GlowChunk(world, x, z);
            GlowChunk prev = chunks.putIfAbsent(key, chunk);
            // if it was created in the intervening time, the earlier one wins
            return prev == null ? chunk : prev;
        }
    }

    /**
     * Checks if the Chunk at the specified coordinates is loaded.
     * @param x The X coordinate.
     * @param z The Z coordinate.
     * @return true if the chunk is loaded, otherwise false.
     */
    public boolean isChunkLoaded(int x, int z) {
        GlowChunk.Key key = new GlowChunk.Key(x, z);
        return chunks.containsKey(key) && chunks.get(key).isLoaded();
    }

    /**
     * Check whether a chunk has locks on it preventing it from being unloaded.
     * @param x The X coordinate.
     * @param z The Z coordinate.
     * @return Whether the chunk is in use.
     */
    public boolean isChunkInUse(int x, int z) {
        GlowChunk.Key key = new GlowChunk.Key(x, z);
        Set<ChunkLock> lockSet = locks.get(key);
        return lockSet != null && lockSet.size() != 0;
    }

    /**
     * Call the ChunkIoService to load a chunk, optionally generating the chunk.
     * @param x The X coordinate of the chunk to load.
     * @param z The Y coordinate of the chunk to load.
     * @param generate Whether to generate the chunk if needed.
     * @return True on success, false on failure.
     */
    public boolean loadChunk(int x, int z, boolean generate) {
        GlowChunk chunk = getChunk(x, z);

        // try to load chunk
        try {
            if (service.read(chunk)) {
                EventFactory.callEvent(new ChunkLoadEvent(chunk, false));
                return true;
            }
        } catch (Exception e) {
            GlowServer.logger.log(Level.SEVERE, "Error while loading chunk (" + x + "," + z + ")", e);
            // an error in chunk reading may have left the chunk in an invalid state
            // (i.e. double initialization errors), so it's forcibly unloaded here
            chunk.unload(false, false);
        }

        // stop here if we can't generate
        if (!generate) {
            return false;
        }

        // get generating
        try {
            generateChunk(chunk, x, z);
        } catch (Throwable ex) {
            GlowServer.logger.log(Level.SEVERE, "Error while generating chunk (" + x + "," + z + ")", ex);
            return false;
        }

        EventFactory.callEvent(new ChunkLoadEvent(chunk, true));

        // right now, forcePopulate takes care of populating chunks that players actually see.
        /*for (int x2 = x - 1; x2 <= x + 1; ++x2) {
            for (int z2 = z - 1; z2 <= z + 1; ++z2) {
                populateChunk(x2, z2, false);
            }
        }*/
        return true;
    }

    /**
     * Unload chunks with no locks on them.
     */
    public void unloadOldChunks() {
        for (Map.Entry<GlowChunk.Key, GlowChunk> entry : chunks.entrySet()) {
            Set<ChunkLock> lockSet = locks.get(entry.getKey());
            if (lockSet == null || lockSet.size() == 0) {
                if (!entry.getValue().unload(true, true)) {
                    GlowServer.logger.warning("Failed to unload chunk " + world.getName() + ":" + entry.getKey());
                }
            }
            // cannot remove old chunks from cache - GlowBlock and GlowBlockState keep references.
            // they must either be changed to look up the chunk again all the time, or this code left out.
            /*if (!entry.getValue().isLoaded()) {
                //GlowServer.logger.info("Removing from cache " + entry.getKey());
                chunks.entrySet().remove(entry);
                locks.remove(entry.getKey());
            }*/
        }
    }

    /**
     * Populate a single chunk if needed.
     */
    private void populateChunk(int x, int z, boolean force) {
        GlowChunk chunk = getChunk(x, z);
        // cancel out if it's already populated
        if (chunk.isPopulated()) {
            return;
        }

        // cancel out if the 3x3 around it isn't available
        for (int x2 = x - 1; x2 <= x + 1; ++x2) {
            for (int z2 = z - 1; z2 <= z + 1; ++z2) {
                if (!getChunk(x2, z2).isLoaded() && (!force || !loadChunk(x2, z2, true))) {
                    return;
                }
            }
        }

        // it might have loaded since before, so check again that it's not already populated
        if (chunk.isPopulated()) {
            return;
        }
        chunk.setPopulated(true);

        Random random = new Random(world.getSeed());
        long xRand = random.nextLong() / 2 * 2 + 1;
        long zRand = random.nextLong() / 2 * 2 + 1;
        random.setSeed((long) x * xRand + (long) z * zRand ^ world.getSeed());

        for (BlockPopulator p : world.getPopulators()) {
            p.populate(world, random, chunk);
        }

        EventFactory.callEvent(new ChunkPopulateEvent(chunk));
    }

    /**
     * Force a chunk to be populated by loading the chunks in an area around it. Used when streaming chunks to players
     * so that they do not have to watch chunks being populated.
     * @param x The X coordinate.
     * @param z The Z coordinate.
     */
    public void forcePopulation(int x, int z) {
        try {
            populateChunk(x, z, true);
        } catch (Throwable ex) {
            GlowServer.logger.log(Level.SEVERE, "Error while populating chunk (" + x + "," + z + ")", ex);
        }
    }

    /**
     * Initialize a single chunk from the chunk generator.
     */
    private void generateChunk(GlowChunk chunk, int x, int z) {
        Random random = new Random((long) x * 341873128712L + (long) z * 132897987541L);
        BiomeGrid biomes = new BiomeGrid();

        // extended sections
        short[][] extSections = generator.generateExtBlockSections(world, random, x, z, biomes);
        if (extSections != null) {
            GlowChunk.ChunkSection[] sections = new GlowChunk.ChunkSection[extSections.length];
            for (int i = 0; i < extSections.length; ++i) {
                // this is sort of messy.
                if (extSections[i] != null) {
                    sections[i] = new GlowChunk.ChunkSection();
                    for (int j = 0; j < extSections[i].length; ++j) {
                        sections[i].types[j] = (char) (extSections[i][j] << 4);
                    }
                    sections[i].recount();
                }
            }
            chunk.initializeSections(sections);
            chunk.setBiomes(biomes.biomes);
            return;
        }

        // normal sections
        byte[][] blockSections = generator.generateBlockSections(world, random, x, z, biomes);
        if (blockSections != null) {
            GlowChunk.ChunkSection[] sections = new GlowChunk.ChunkSection[blockSections.length];
            for (int i = 0; i < blockSections.length; ++i) {
                // this is sort of messy.
                if (blockSections[i] != null) {
                    sections[i] = new GlowChunk.ChunkSection();
                    for (int j = 0; j < blockSections[i].length; ++j) {
                        sections[i].types[j] = (char) (blockSections[i][j] << 4);
                    }
                    sections[i].recount();
                }
            }
            chunk.initializeSections(sections);
            chunk.setBiomes(biomes.biomes);
            return;
        }

        // deprecated flat generation
        byte[] types = generator.generate(world, random, x, z);
        GlowChunk.ChunkSection[] sections = new GlowChunk.ChunkSection[8];
        for (int sy = 0; sy < sections.length; ++sy) {
            GlowChunk.ChunkSection sec = new GlowChunk.ChunkSection();
            int by = 16 * sy;
            for (int cx = 0; cx < 16; ++cx) {
                for (int cz = 0; cz < 16; ++cz) {
                    for (int cy = by; cy < by + 16; ++cy) {
                        char type = (char) types[(cx * 16 + cz) * 128 + cy];
                        sec.types[sec.index(cx, cy, cz)] = (char) (type << 4);
                    }
                }
            }
            sec.recount();
            sections[sy] = sec;
        }
        chunk.initializeSections(sections);
        chunk.setBiomes(biomes.biomes);
    }

    /**
     * Forces generation of the given chunk.
     * @param x The X coordinate.
     * @param z The Z coordinate.
     * @return Whether the chunk was successfully regenerated.
     */
    public boolean forceRegeneration(int x, int z) {
        GlowChunk chunk = getChunk(x, z);

        if (chunk == null || !chunk.unload(false, false)) {
            return false;
        }

        chunk.setPopulated(false);
        try {
            generateChunk(chunk, x, z);
            populateChunk(x, z, false)// should this be forced?
        } catch (Throwable ex) {
            GlowServer.logger.log(Level.SEVERE, "Error while regenerating chunk (" + x + "," + z + ")", ex);
            return false;
        }
        return true;
    }

    /**
     * Gets a list of loaded chunks.
     * @return The currently loaded chunks.
     */
    public GlowChunk[] getLoadedChunks() {
        ArrayList<GlowChunk> result = new ArrayList<>();
        for (GlowChunk chunk : chunks.values()) {
            if (chunk.isLoaded()) {
                result.add(chunk);
            }
        }
        return result.toArray(new GlowChunk[result.size()]);
    }

    /**
     * Performs the save for the given chunk using the storage provider.
     * @param chunk The chunk to save.
     */
    public boolean performSave(GlowChunk chunk) {
        if (chunk.isLoaded()) {
            try {
                service.write(chunk);
                return true;
            } catch (IOException ex) {
                GlowServer.logger.log(Level.SEVERE, "Error while saving " + chunk, ex);
                return false;
            }
        }
        return false;
    }

    /**
     * A BiomeGrid implementation for chunk generation.
     */
    private class BiomeGrid implements ChunkGenerator.BiomeGrid {
        private final byte[] biomes = new byte[256];

        @Override
        public Biome getBiome(int x, int z) {
            return GlowBiome.getBiome(biomes[z * 16 + x]);
        }

        @Override
        public void setBiome(int x, int z, Biome bio) {
            biomes[z * 16 + x] = (byte) GlowBiome.getId(bio);
        }
    }

    /**
     * Look up the set of locks on a given chunk.
     * @param key The chunk key.
     * @return The set of locks for that chunk.
     */
    private Set<ChunkLock> getLockSet(GlowChunk.Key key) {
        if (locks.containsKey(key)) {
            return locks.get(key);
        } else {
            // only create chunk if it's not in the map already
            Set<ChunkLock> set = new HashSet<>();
            Set<ChunkLock> prev = locks.putIfAbsent(key, set);
            // if it was created in the intervening time, the earlier one wins
            return prev == null ? set : prev;
        }
    }

    /**
     * A group of locks on chunks to prevent them from being unloaded while in use.
     */
    public static class ChunkLock implements Iterable<GlowChunk.Key> {
        private final ChunkManager cm;
        private final String desc;
        private final Set<GlowChunk.Key> keys = new HashSet<>();

        public ChunkLock(ChunkManager cm, String desc) {
            this.cm = cm;
            this.desc = desc;
        }

        public void acquire(GlowChunk.Key key) {
            if (keys.contains(key)) return;
            keys.add(key);
            cm.getLockSet(key).add(this);
            //GlowServer.logger.info(this + " acquires " + key);
        }

        public void release(GlowChunk.Key key) {
            if (!keys.contains(key)) return;
            keys.remove(key);
            cm.getLockSet(key).remove(this);
            //GlowServer.logger.info(this + " releases " + key);
        }

        public void clear() {
            for (GlowChunk.Key key : keys) {
                cm.getLockSet(key).remove(this);
                //GlowServer.logger.info(this + " clearing " + key);
            }
            keys.clear();
        }

        @Override
        public String toString() {
            return "ChunkLock{" + desc + "}";
        }

        @Override
        public Iterator<GlowChunk.Key> iterator() {
            return keys.iterator();
        }
    }
}
TOP

Related Classes of net.glowstone.ChunkManager$ChunkLock

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.