Package rakama.worldtools.io

Source Code of rakama.worldtools.io.ChunkManager$ChunkCache

/*
* Copyright (c) 2012, RamsesA <ramsesakama@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/

package rakama.worldtools.io;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import rakama.worldtools.data.Chunk;
import rakama.worldtools.data.entity.EntityFactory;
import rakama.worldtools.light.ChunkRelighter;
import rakama.worldtools.util.PriorityCache;


public class ChunkManager
{
    protected final static int default_max_cache_size = 1024;
    protected final static int default_window_scale = 3;
    protected final static int minimum_cleanup_size = 32;
    protected final static int priority_access = 10000;
    protected final static int priority_light = 5000;
    protected final static int priority_read = 100;
    protected final boolean debug = false;
   
    private final ChunkAccess access;
    private final ManagedChunk[] window;
    private final ChunkCache cache;
    private final ChunkRelighter relighter;
    private final List<ManagedChunk> cleanup;
   
    private final int windowSize, windowScale, windowMask;
    private int windowMinX, windowMinZ, reads, writes;
    private boolean lightingEnabled, readOnly;
    private Thread shutdownHook;
   
    public ChunkManager(ChunkAccess access)
    {
        this(access, false, true);
    }

    public ChunkManager(ChunkAccess access, boolean readOnly)
    {
        this(access, readOnly, readOnly, default_window_scale, default_max_cache_size);
    }
   
    public ChunkManager(ChunkAccess access, boolean readOnly, boolean lightingEnabled)
    {
        this(access, readOnly, lightingEnabled, default_window_scale, default_max_cache_size);
    }
       
    protected ChunkManager(ChunkAccess access, boolean ro, boolean le, int windowScale, int cacheSize)
    {
        this.access = access;
        this.windowScale = windowScale;
        this.windowSize = 1 << windowScale;
        this.windowMask = bitmask(windowScale);
        this.window = new ManagedChunk[windowSize * windowSize];
        this.cache = new ChunkCache(cacheSize);
        this.relighter = new ChunkRelighter();
        this.cleanup = new LinkedList<ManagedChunk>();
        this.lightingEnabled = true;
       
        shutdownHook = new CloseOpenChunks(this);
        Runtime.getRuntime().addShutdownHook(shutdownHook);
    }

    public boolean isReadOnly()
    {
        return readOnly;
    }
   
    public boolean isLightingEnabled()
    {
        return lightingEnabled;
    }
   
    public Chunk getChunk(int x, int z)
    {
        return getChunk(x, z, false);
    }
   
    public Chunk getChunk(int x, int z, boolean create)
    {
        ManagedChunk chunk = getChunk(x, z, priority_access, true, create);
        doCleanup(minimum_cleanup_size);
        return chunk;
    }

    public Chunk getChunk(int x, int z, boolean create, boolean relight)
    {
        ManagedChunk chunk = getChunk(x, z, priority_access, true, create);       
        if(chunk != null && lightingEnabled && chunk.needsRelight())
            relightChunk(chunk);
        doCleanup(minimum_cleanup_size);
        return chunk;
    }
   
    protected ManagedChunk getChunk(int x, int z, int priority, boolean moveWindow, boolean create)
    {     
        // try window       
        int winX = x - windowMinX;
        int winZ = z - windowMinZ;
        int winIndex;

        if(inWindow(winX, winZ))
        {
            winIndex = winX + (winZ << windowScale);      
            ManagedChunk chunk = window[winIndex];      
            if(chunk != null)
                return chunk;
        }
        else if(moveWindow)
        {
            setWindow(x, z);
            winX = x - windowMinX;
            winZ = z - windowMinZ;
            winIndex = winX + (winZ << windowScale);
        }
        else
            winIndex = -1;

        if(debug)
            log("WINDOW_MISS " + x + " " + z);
       
        // try soft cache
        cache.decay(1);       
        ManagedChunk chunk = cache.get(x, z, priority);
       
        if(chunk != null)
        {
            // place chunk in window
            if(winIndex > -1)
                window[winIndex] = chunk;    
           
            return chunk;
        }

        if(debug)
            log("CACHE_MISS " + x + " " + z);
       
        // try chunk access       
        chunk = readChunk(x, z);
       
        if(chunk == null)
        {   
            if(!create || readOnly)
                return null;    
           
            if(debug)
                log("NEW_CHUNK " + x + " " + z);
         
            chunk = new ManagedChunk(x, z, this);
            chunk.invalidateFile();
        }
       
        // place chunk in cache
        cache.put(chunk, priority);

        // place chunk in window
        if(winIndex > -1)
            window[winIndex] = chunk;
       
        return chunk;
    }
   
    private void setWindow(int x0, int z0)
    {       
        if(lightingEnabled)
            invalidateLights();
       
        int offset = windowSize >> 1;
        windowMinX = x0 - offset;
        windowMinZ = z0 - offset;
        Arrays.fill(window, null);
    }
   
    private void invalidateLights()
    {
        for(ManagedChunk chunk : window)
            if(chunk != null && chunk.needsNeighborNotify())
                notifyNeighbors(chunk);
    }
   
    private void notifyNeighbors(ManagedChunk chunk)
    {       
        int x = chunk.getX();
        int z = chunk.getZ();
       
        notifyIfExists(getChunk(x - 1, z - 1, priority_light, false, false));
        notifyIfExists(getChunk(x, z - 1, priority_light, false, false));
        notifyIfExists(getChunk(x + 1, z - 1, priority_light, false, false));
        notifyIfExists(getChunk(x - 1, z, priority_light, false, false));
        notifyIfExists(getChunk(x + 1, z, priority_light, false, false));
        notifyIfExists(getChunk(x - 1, z + 1, priority_light, false, false));
        notifyIfExists(getChunk(x, z + 1, priority_light, false, false));
        notifyIfExists(getChunk(x + 1, z + 1, priority_light, false, false));
       
        cache.refresh(chunk, priority_light);
        chunk.validateNeighborNotify();
    }
   
    private void notifyIfExists(ManagedChunk chunk)
    {
        if(chunk != null)
            chunk.invalidateLights();
    }
   
    private final boolean inWindow(int x, int z)
    {
        return (x & windowMask) == x && (z & windowMask) == z;
    }

    protected void requestCleanup(ManagedChunk chunk)
    {
        if(chunk == null)
            return;
       
        synchronized(cleanup)
        {
            cleanup.add(chunk);
        }
    }
   
    private void doCleanup(int minimumQueueSize)
    {
        if(cleanup.size() < minimumQueueSize)
            return;

        synchronized(cleanup)
        {
            List<ManagedChunk> remove = new ArrayList<ManagedChunk>(cleanup);
            cleanup.clear();
           
            for(ManagedChunk chunk : remove)
                flushChanges(chunk);
        }
    }

    private boolean flushChanges(ManagedChunk chunk)
    {
        if(debug)
            log("FLUSH_CHANGES " + chunk.getX() + " " + chunk.getZ());
   
        if(readOnly)
            return false;
       
        boolean pendingChanges = false;

        if(chunk.needsNeighborNotify())
        {
            notifyNeighbors(chunk);
            pendingChanges = true;
        }
       
        if(lightingEnabled && chunk.needsRelight())
            relightChunk(chunk);
       
        if(chunk.needsWrite())
            writeChunk(chunk);

        return pendingChanges;
    }
       
    private void relightChunk(ManagedChunk chunk)
    {
        int x0 = chunk.getX();
        int z0 = chunk.getZ();
       
        Chunk[] local = new Chunk[9];
       
        local[4] = chunk;
       
        for(int x = 0; x < 3; x++)
        {
            for(int z = 0; z < 3; z++)
            {
                int index = x + z * 3;               
                if(index == 4)
                    continue;
               
                local[index] = getChunk(x + x0 - 1, z + z0 - 1, priority_read, false, false);
            }
        }

        relighter.lightChunks(local);
        chunk.validateLights();
    }
   
    public void closeAll()
    {
        unloadAll();
        access.closeAll();
    }

    private synchronized void unloadAll()
    {       
        if(debug)
            log("UNLOADING_CACHE *");

        invalidateLights();
           
        while(!cache.isEmpty())
        {
            cache.clear();
            flushWeakReferences();
            doCleanup(0);
        }

        Arrays.fill(window, null);

        if(debug)
            log("CACHE_UNLOADED");
    }
   
    private void flushWeakReferences()
    {       
        List<ManagedChunk> flush = new ArrayList<ManagedChunk>();
       
        for(WeakReference<ManagedChunk> ref : cache.getWeakReferences())
        {
            ManagedChunk chunk = ref.get();
            if(chunk != null)
                flush.add(chunk);
        }
       
        for(ManagedChunk chunk : flush)
            flushChanges(chunk);
    }

    private ManagedChunk readChunk(int x, int z)
    {
        try
        {
            ManagedChunk chunk = access.readChunk(x, z, this);
            if(chunk != null)
                reads++;
            return chunk;
        }
        catch(IOException e)
        {
            e.printStackTrace();
            return null;
        }
    }

    private boolean writeChunk(ManagedChunk chunk)
    {
        try
        {
            access.writeChunk(chunk);
            chunk.validateFile();
            writes++;
            return true;
        }
        catch(IOException e)
        {
            e.printStackTrace();
            return false;
        }
    }

    private static int bitmask(int bits)
    {
        int mask = 0;

        for(int i = 0; i < bits; i++)
            mask |= 1 << i;

        return mask;
    }

    public EntityFactory getEntityFactory()
    {
        return access.getEntityFactory();
    }
       
    public Collection<RegionInfo> getRegions()
    {
        return access.getRegions();
    }

    public int getCacheSize()
    {
        return cache.size();
    }
   
    public int getNumReads()
    {
        return reads;
    }

    public int getNumWrites()
    {
        return writes;
    }

    protected ChunkAccess getChunkAccess()
    {
        return access;
    }
   
    protected final void log(String str)
    {
        System.out.println(str);
    }

    @Override
    protected void finalize() throws Exception
    {
        closeAll();
        Runtime.getRuntime().removeShutdownHook(shutdownHook);
    }
   
    final class ChunkCache extends PriorityCache<ChunkID, ManagedChunk>
    {           
        public ChunkCache(int maxCapacity)
        {
            super(maxCapacity, 0.9, true);
        }

        public ManagedChunk get(int x, int z, int priority)
        {
            return super.get(new ChunkID(x, z), priority);
        }

        public void put(ManagedChunk chunk, int priority)
        {
            super.put(chunk.getID(), chunk, priority);
        }
           
        public void refresh(ManagedChunk chunk, int priority)
        {
            super.refresh(chunk.getID(), priority);
        }
       
        protected void expired(ChunkID key, ManagedChunk value)
        {
            if(value.isDirty())
                requestCleanup(value);
        }
    }
}

final class CloseOpenChunks extends Thread
{
    WeakReference<ChunkManager> ref;
   
    public CloseOpenChunks(ChunkManager manager)
    {
        this.ref = new WeakReference<ChunkManager>(manager);
    }

    public void run()
    {
        ChunkManager manager = ref.get();       
        if(manager == null)
            return;
       
        System.gc();       
        manager.closeAll();
    }   
}
TOP

Related Classes of rakama.worldtools.io.ChunkManager$ChunkCache

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.