<commit_before>package sx.lambda.voxel.world;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.g3d.Material;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.ModelInstance;
import com.badlogic.gdx.graphics.g3d.attributes.TextureAttribute;
import com.badlogic.gdx.graphics.g3d.utils.MeshBuilder;
import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder;
import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.IntMap;
import io.netty.util.internal.ConcurrentSet;
import sx.lambda.voxel.VoxelGameClient;
import sx.lambda.voxel.api.VoxelGameAPI;
import sx.lambda.voxel.api.events.worldgen.EventFinishChunkGen;
import sx.lambda.voxel.block.Block;
import sx.lambda.voxel.block.Side;
import sx.lambda.voxel.entity.Entity;
import sx.lambda.voxel.entity.EntityPosition;
import sx.lambda.voxel.util.Vec3i;
import sx.lambda.voxel.world.chunk.Chunk;
import sx.lambda.voxel.world.chunk.IChunk;
import sx.lambda.voxel.world.generation.ChunkGenerator;
import sx.lambda.voxel.world.generation.SimplexChunkGenerator;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingDeque;

public class World implements IWorld {

    private static final int CHUNK_SIZE = 16;

    private static final int WORLD_HEIGHT = 128;

    private static final int SEA_LEVEL = 64;

    private final IntMap<IntMap<IChunk>> chunkMapX = new IntMap<>();
    private final Set<IChunk> chunkList = new ConcurrentSet<>();
    private List<IChunk> sortedChunkList;

    private IChunk lastPlayerChunk;

    private static final float GRAVITY = 6f;

    private static final float TERMINAL_VELOCITY = 56;

    private final ChunkGenerator chunkGen;

    private final boolean remote, server;

    private List<Entity> loadedEntities = new CopyOnWriteArrayList<>();

    private Set<IChunk> chunksToRerender = Collections.newSetFromMap(new ConcurrentHashMap<IChunk, Boolean>());

    private Queue<int[]> sunlightQueue = new ConcurrentLinkedQueue<>();
    private Queue<int[]> sunlightRemovalQueue = new ConcurrentLinkedQueue<>();

    private ModelBatch modelBatch;
    private ModelInstance skybox;
    private Model skyboxModel;
    private Texture skyboxTexture;

    private boolean shouldUpdateLight;

    private int lightUpdaters;

    private int chunksMeshing;

    public World(boolean remote, boolean server) {
        this.remote = remote;
        this.server = server;
        if (!remote) {
            this.chunkGen = new SimplexChunkGenerator(this, 200, new Random().nextInt());
        } else {
            this.chunkGen = null;
        }
    }

    public int getChunkSize() {
        return CHUNK_SIZE;
    }

    public int getHeight() {
        return WORLD_HEIGHT;
    }

    public IChunk getChunkAtPosition(Vec3i position) {
        return getChunkAtPosition(position.x, position.z);
    }

    @Override
    public IChunk getChunkAtPosition(int x, int z) {
        x = getChunkPosition(x);
        z = getChunkPosition(z);

        IntMap<IChunk> zMap = null;
        try {
            zMap = this.chunkMapX.get(x);
        } catch(ArrayIndexOutOfBoundsException ex) { // Sometimes the libgdx intmap will give aioob. Not sure why.
            ex.printStackTrace();
        }
        if(zMap == null)
            return null;

        IChunk chunk = null;
        try {
            chunk = zMap.get(z);
        } catch(ArrayIndexOutOfBoundsException ex) { // Sometimes the libgdx intmap will give aioob. Not sure why.
            ex.printStackTrace();
        }

        return chunk;
    }

    private void removeChunkFromMap(Vec3i pos) {
        removeChunkFromMap(pos.x, pos.z);
    }

    private void removeChunkFromMap(int x, int z) {
        IntMap<IChunk> zMap = this.chunkMapX.get(x);
        if(zMap == null)
            return;

        zMap.remove(z);
    }

    @Override
    public void render() {
        assert !server;

        if (modelBatch == null) {
            modelBatch = new ModelBatch(Gdx.files.internal("shaders/gdx/world.vert.glsl"), Gdx.files.internal("shaders/gdx/world.frag.glsl"));
        }
        if(skybox == null) {
            skybox = createSkybox();
        }

        if (lightUpdaters < 2 && (sunlightQueue.size() > 0 || sunlightRemovalQueue.size() > 0 || shouldUpdateLight)) {
            processLightQueue(); // If a chunk is doing its rerender, we want it to have the most recent lighting possible
        }
        for (IChunk c : chunksToRerender) {
            if(VoxelGameClient.getInstance().getPlayer().getPosition().planeDistance(c.getStartPosition().x, c.getStartPosition().z) <=
                    VoxelGameClient.getInstance().getSettingsManager().getVisualSettings().getViewDistance()*CHUNK_SIZE) {
                c.rerender();
                chunksToRerender.remove(c);
            }
        }

        final IChunk playerChunk = getChunkAtPosition(
                MathUtils.floor(VoxelGameClient.getInstance().getPlayer().getPosition().getX()),
                MathUtils.floor(VoxelGameClient.getInstance().getPlayer().getPosition().getZ()));
        if(playerChunk != null && (playerChunk != lastPlayerChunk || (sortedChunkList != null && chunkList.size() != sortedChunkList.size()))) {
            sortedChunkList = new ArrayList<>();
            for(IChunk c : chunkList) {
                if (VoxelGameClient.getInstance().getPlayer().getPosition().planeDistance(c.getStartPosition().x, c.getStartPosition().z) <=
                        VoxelGameClient.getInstance().getSettingsManager().getVisualSettings().getViewDistance() * CHUNK_SIZE) {
                    sortedChunkList.add(c);
                }
            }
            Collections.sort(sortedChunkList, new Comparator<IChunk>() {
                @Override
                public int compare(IChunk c1, IChunk c2) {
                    int xDiff1 = playerChunk.getStartPosition().x - c1.getStartPosition().x;
                    int zDiff1 = playerChunk.getStartPosition().z - c1.getStartPosition().z;
                    int xDiff2 = playerChunk.getStartPosition().x - c2.getStartPosition().x;
                    int zDiff2 = playerChunk.getStartPosition().z - c2.getStartPosition().z;
                    int sqDist2 = xDiff2*xDiff2 + zDiff2*zDiff2;
                    int sqDist1 = xDiff1*xDiff1 + zDiff1*zDiff1;
                    return sqDist2 - sqDist1;
                }
            });
            lastPlayerChunk = playerChunk;
        }

        if(sortedChunkList != null) {
            long renderStartNS = System.nanoTime();
            modelBatch.begin(VoxelGameClient.getInstance().getCamera());
            float playerX = VoxelGameClient.getInstance().getPlayer().getPosition().getX(),
                    playerY = VoxelGameClient.getInstance().getPlayer().getPosition().getY(),
                    playerZ = VoxelGameClient.getInstance().getPlayer().getPosition().getZ();
            skybox.transform.translate(playerX, playerY, playerZ);
            modelBatch.render(skybox);
            skybox.transform.translate(-playerX, -playerY, -playerZ);
            if(VoxelGameClient.getInstance().isWireframe())
                Gdx.gl.glLineWidth(5);
            boolean[] chunkVisible = new boolean[sortedChunkList.size()];
            int chunkNum = 0;
            for (IChunk c : sortedChunkList) {
                int x = c.getStartPosition().x;
                int z = c.getStartPosition().z;
                int halfWidth = getChunkSize()/2;
                int midX = x + halfWidth;
                int midZ = z + halfWidth;
                int midY = c.getHighestPoint()/2;
                boolean visible = VoxelGameClient.getInstance().getGameRenderer().getFrustum().boundsInFrustum(midX, midY, midZ, halfWidth, midY, halfWidth);
                chunkVisible[chunkNum] = visible;
                chunkNum++;
                if(visible) {
                    c.render(modelBatch);
                }
            }
            chunkNum = 0;
            for (IChunk c : sortedChunkList) {
                if(chunkVisible[chunkNum]) {
                    c.renderTranslucent(modelBatch);
                }
                chunkNum++;
            }
            modelBatch.end();
            if (VoxelGameClient.getInstance().numChunkRenders == 100) {  // Reset every 100 renders
                VoxelGameClient.getInstance().numChunkRenders = 0;
                VoxelGameClient.getInstance().chunkRenderTimes = 0;
            }
            VoxelGameClient.getInstance().chunkRenderTimes += (int) (System.nanoTime() - renderStartNS);
            VoxelGameClient.getInstance().numChunkRenders++;
        }
    }

    @Override
    public void loadChunks(EntityPosition playerPosition, int viewDistance) {
        if (!remote) { //don't gen chunks if we're not local
            this.getChunksInRange(playerPosition, viewDistance);
        }

    }

    @Override
    public int getSeaLevel() {
        return SEA_LEVEL;
    }

    @Override
    public int getChunkPosition(float value) {
        int subtraction = MathUtils.floor(value) & (CHUNK_SIZE-1);
        return MathUtils.floor(value - subtraction);
    }

    @Override
    public float getGravity() {
        return GRAVITY;
    }

    @Override
    public float applyGravity(float velocity, long ms) {
        if (ms < 0) ms = 0 - ms;
        return Math.max(-TERMINAL_VELOCITY, velocity - (getGravity() / 1000) * (ms / 10f));
    }

    @Override
    public void removeBlock(int x, int y, int z) {
        synchronized (this) {
            final IChunk c = this.getChunkAtPosition(x, z);
            if (c != null) {
                x &= getChunkSize()-1;
                y &= getChunkSize()-1;
                c.removeBlock(x, y, z);
                c.setMeta((short) 0, x, y, z);
            }
        }
    }

    @Override
    public void addBlock(int block, int x, int y, int z) {
        synchronized (this) {
            final IChunk c = this.getChunkAtPosition(x, z);
            if(c != null)
                c.setBlock(block, x & (getChunkSize() - 1), y, z * (getChunkSize() - 1));
        }
    }

    @Override
    public IChunk[] getChunksInRange(EntityPosition epos, int viewDistance) {
        List<IChunk> chunkList = new ArrayList<>();
        int playerChunkX = getChunkPosition(epos.getX());
        int playerChunkZ = getChunkPosition(epos.getZ());
        int range = viewDistance * CHUNK_SIZE;
        for (int x = playerChunkX - range; x < playerChunkX + range; x += CHUNK_SIZE) {
            for (int z = playerChunkZ - range; z < playerChunkZ + range; z += CHUNK_SIZE) {
                chunkList.add(loadChunk(x, z));
            }
        }
        return chunkList.toArray(new IChunk[chunkList.size()]);
    }

    @Override
    public void addChunk(final IChunk chunk) {
        IChunk c = getChunkAtPosition(chunk.getStartPosition());
        if (c != null) {
            removeChunkFromMap(chunk.getStartPosition());
            this.chunkList.remove(c);
        }
        addChunk(chunk, chunk.getStartPosition().x, chunk.getStartPosition().z);

        addSun(chunk);
    }

    @Override
    public List<Entity> getLoadedEntities() {
        return this.loadedEntities;
    }

    private void addChunk(IChunk chunk, int x, int z) {
        IntMap<IChunk> foundChunkMapZ = this.chunkMapX.get(x);
        if(foundChunkMapZ == null) {
            foundChunkMapZ = new IntMap<>();
            this.chunkMapX.put(x, foundChunkMapZ);
        }
        foundChunkMapZ.put(z, chunk);
        this.chunkList.add(chunk);
    }

    private IChunk loadChunk(int startX, int startZ) {
        IChunk foundChunk = getChunkAtPosition(startX, startZ);
        if (foundChunk == null && !remote) {
            final IChunk c = new Chunk(this, new Vec3i(startX, 0, startZ), VoxelGameAPI.instance.getBiomeByID(0));
            VoxelGameAPI.instance.getEventManager().push(new EventFinishChunkGen(c));
            addChunk(c, startX, startZ);
            addSun(c);
            return c;
        } else {
            return foundChunk;
        }
    }

    private void addSun(IChunk c) {
        for (int x = 0; x < CHUNK_SIZE; x++) {
            for (int z = 0; z < CHUNK_SIZE; z++) {
                c.setSunlight(x, WORLD_HEIGHT - 1, z, 16);
                addToSunlightQueue(c.getStartPosition().x + x, WORLD_HEIGHT - 1, c.getStartPosition().z + z);
            }
        }
        c.finishChangingSunlight();
    }

    public void addEntity(Entity e) {
        loadedEntities.add(e);
    }


    @Override
    public void rerenderChunk(IChunk c) {
        if(c == null)
            return;
        chunksToRerender.add(c);
    }

    @Override
    public ChunkGenerator getChunkGen() {
        return this.chunkGen;
    }

    /**
     * Add a block to a list of blocks to process sunlight for
     * The block at the position passed should be translucent or null and have a sunlight level greater than 0
     */
    @Override
    public void addToSunlightQueue(int x, int y, int z) {
        sunlightQueue.add(new int[]{x, y, z});
    }

    @Override
    public void addToSunlightRemovalQueue(int x, int y, int z) {
        sunlightRemovalQueue.add(new int[]{x, y, z});
    }

    @Override
    public void processLightQueue() {
        if (sunlightQueue.isEmpty() && sunlightRemovalQueue.isEmpty())
            return;
        shouldUpdateLight = true;
        if (lightUpdaters < 2) {
            lightUpdaters++;
            new Thread("Light update") {
                @Override
                public void run() {
                    shouldUpdateLight = false;

                    processLightRemovalQueue();

                    Queue<IChunk> changedChunks = new LinkedBlockingDeque<>();
                    int[] pos;
                    while ((pos = sunlightQueue.poll()) != null) {
                        int x = pos[0];
                        int cx = x & (CHUNK_SIZE-1);
                        int y = pos[1];
                        int z = pos[2];
                        int cz = z & (CHUNK_SIZE-1);
                        IChunk posChunk = getChunkAtPosition(x, z);
                        if (posChunk == null) {
                            continue;
                        }
                        int ll = posChunk.getSunlight(cx, y, cz);

                        // Spread off to each side
                        for(Side s : Side.values()) {
                            int nextLL = ll - 1; // Decayed light level for the spread
                            int sx = x; // Side x coord
                            int sy = y; // Side y coord
                            int sz = z; // Side z coord
                            int scx = cx; // Chunk-relative side x coord
                            int scz = cz; // Chunk-relative side z coord
                            IChunk sChunk = posChunk;

                            // Offset values based on side
                            switch(s) {
                                case TOP:
                                    sy += 1;
                                    break;
                                case BOTTOM:
                                    sy -= 1;
                                    break;
                                case WEST:
                                    sx -= 1;
                                    scx -= 1;
                                    break;
                                case EAST:
                                    sx += 1;
                                    scx += 1;
                                    break;
                                case NORTH:
                                    sz += 1;
                                    scz += 1;
                                    break;
                                case SOUTH:
                                    sz -= 1;
                                    scz -= 1;
                                    break;
                            }
                            if(sy < 0)
                                continue;
                            if(sy > WORLD_HEIGHT-1)
                                continue;

                            // Select the correct chunk
                            if(scz < 0) {
                                scz += CHUNK_SIZE;
                                sChunk = getChunkAtPosition(sx, sz);
                            } else if(scz > CHUNK_SIZE-1) {
                                scz -= CHUNK_SIZE;
                                sChunk = getChunkAtPosition(sx, sz);
                            }
                            if(scx < 0) {
                                scx += CHUNK_SIZE;
                                sChunk = getChunkAtPosition(sx, sz);
                            } else if(scx > CHUNK_SIZE-1) {
                                scx -= CHUNK_SIZE;
                                sChunk = getChunkAtPosition(sx, sz);
                            }

                            if(sChunk == null)
                                continue;

                            // Spread lighting
                            Block sBlock = sChunk.getBlock(scx, sy, scz);
                            // When spreading down, lighting at max level does not decay
                            if(s == Side.BOTTOM) {
                                Block block = posChunk.getBlock(cx, y, cz); // Block being spread from
                                if (ll == 16 && (block == null || block.decreasesLight()))
                                    nextLL = 16;
                            }
                            if(sBlock == null || sBlock.doesLightPassThrough() || !sBlock.decreasesLight()) {
                                if(sChunk.getSunlight(scx, sy, scz) < nextLL) {
                                    sChunk.setSunlight(scx, sy, scz, nextLL);
                                    addToSunlightQueue(sx, sy, sz);
                                    if(!changedChunks.contains(sChunk))
                                        changedChunks.add(sChunk);
                                }
                            }<commit_msg>[Lighting] Don't check if queue already contains a chunk before adding it<commit_after>package sx.lambda.voxel.world;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.g3d.Material;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.ModelInstance;
import com.badlogic.gdx.graphics.g3d.attributes.TextureAttribute;
import com.badlogic.gdx.graphics.g3d.utils.MeshBuilder;
import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder;
import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.IntMap;
import io.netty.util.internal.ConcurrentSet;
import sx.lambda.voxel.VoxelGameClient;
import sx.lambda.voxel.api.VoxelGameAPI;
import sx.lambda.voxel.api.events.worldgen.EventFinishChunkGen;
import sx.lambda.voxel.block.Block;
import sx.lambda.voxel.block.Side;
import sx.lambda.voxel.entity.Entity;
import sx.lambda.voxel.entity.EntityPosition;
import sx.lambda.voxel.util.Vec3i;
import sx.lambda.voxel.world.chunk.Chunk;
import sx.lambda.voxel.world.chunk.IChunk;
import sx.lambda.voxel.world.generation.ChunkGenerator;
import sx.lambda.voxel.world.generation.SimplexChunkGenerator;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingDeque;

public class World implements IWorld {

    private static final int CHUNK_SIZE = 16;

    private static final int WORLD_HEIGHT = 128;

    private static final int SEA_LEVEL = 64;

    private final IntMap<IntMap<IChunk>> chunkMapX = new IntMap<>();
    private final Set<IChunk> chunkList = new ConcurrentSet<>();
    private List<IChunk> sortedChunkList;

    private IChunk lastPlayerChunk;

    private static final float GRAVITY = 6f;

    private static final float TERMINAL_VELOCITY = 56;

    private final ChunkGenerator chunkGen;

    private final boolean remote, server;

    private List<Entity> loadedEntities = new CopyOnWriteArrayList<>();

    private Set<IChunk> chunksToRerender = Collections.newSetFromMap(new ConcurrentHashMap<IChunk, Boolean>());

    private Queue<int[]> sunlightQueue = new ConcurrentLinkedQueue<>();
    private Queue<int[]> sunlightRemovalQueue = new ConcurrentLinkedQueue<>();

    private ModelBatch modelBatch;
    private ModelInstance skybox;
    private Model skyboxModel;
    private Texture skyboxTexture;

    private boolean shouldUpdateLight;

    private int lightUpdaters;

    private int chunksMeshing;

    public World(boolean remote, boolean server) {
        this.remote = remote;
        this.server = server;
        if (!remote) {
            this.chunkGen = new SimplexChunkGenerator(this, 200, new Random().nextInt());
        } else {
            this.chunkGen = null;
        }
    }

    public int getChunkSize() {
        return CHUNK_SIZE;
    }

    public int getHeight() {
        return WORLD_HEIGHT;
    }

    public IChunk getChunkAtPosition(Vec3i position) {
        return getChunkAtPosition(position.x, position.z);
    }

    @Override
    public IChunk getChunkAtPosition(int x, int z) {
        x = getChunkPosition(x);
        z = getChunkPosition(z);

        IntMap<IChunk> zMap = null;
        try {
            zMap = this.chunkMapX.get(x);
        } catch(ArrayIndexOutOfBoundsException ex) { // Sometimes the libgdx intmap will give aioob. Not sure why.
            ex.printStackTrace();
        }
        if(zMap == null)
            return null;

        IChunk chunk = null;
        try {
            chunk = zMap.get(z);
        } catch(ArrayIndexOutOfBoundsException ex) { // Sometimes the libgdx intmap will give aioob. Not sure why.
            ex.printStackTrace();
        }

        return chunk;
    }

    private void removeChunkFromMap(Vec3i pos) {
        removeChunkFromMap(pos.x, pos.z);
    }

    private void removeChunkFromMap(int x, int z) {
        IntMap<IChunk> zMap = this.chunkMapX.get(x);
        if(zMap == null)
            return;

        zMap.remove(z);
    }

    @Override
    public void render() {
        assert !server;

        if (modelBatch == null) {
            modelBatch = new ModelBatch(Gdx.files.internal("shaders/gdx/world.vert.glsl"), Gdx.files.internal("shaders/gdx/world.frag.glsl"));
        }
        if(skybox == null) {
            skybox = createSkybox();
        }

        if (lightUpdaters < 2 && (sunlightQueue.size() > 0 || sunlightRemovalQueue.size() > 0 || shouldUpdateLight)) {
            processLightQueue(); // If a chunk is doing its rerender, we want it to have the most recent lighting possible
        }
        for (IChunk c : chunksToRerender) {
            if(VoxelGameClient.getInstance().getPlayer().getPosition().planeDistance(c.getStartPosition().x, c.getStartPosition().z) <=
                    VoxelGameClient.getInstance().getSettingsManager().getVisualSettings().getViewDistance()*CHUNK_SIZE) {
                c.rerender();
                chunksToRerender.remove(c);
            }
        }

        final IChunk playerChunk = getChunkAtPosition(
                MathUtils.floor(VoxelGameClient.getInstance().getPlayer().getPosition().getX()),
                MathUtils.floor(VoxelGameClient.getInstance().getPlayer().getPosition().getZ()));
        if(playerChunk != null && (playerChunk != lastPlayerChunk || (sortedChunkList != null && chunkList.size() != sortedChunkList.size()))) {
            sortedChunkList = new ArrayList<>();
            for(IChunk c : chunkList) {
                if (VoxelGameClient.getInstance().getPlayer().getPosition().planeDistance(c.getStartPosition().x, c.getStartPosition().z) <=
                        VoxelGameClient.getInstance().getSettingsManager().getVisualSettings().getViewDistance() * CHUNK_SIZE) {
                    sortedChunkList.add(c);
                }
            }
            Collections.sort(sortedChunkList, new Comparator<IChunk>() {
                @Override
                public int compare(IChunk c1, IChunk c2) {
                    int xDiff1 = playerChunk.getStartPosition().x - c1.getStartPosition().x;
                    int zDiff1 = playerChunk.getStartPosition().z - c1.getStartPosition().z;
                    int xDiff2 = playerChunk.getStartPosition().x - c2.getStartPosition().x;
                    int zDiff2 = playerChunk.getStartPosition().z - c2.getStartPosition().z;
                    int sqDist2 = xDiff2*xDiff2 + zDiff2*zDiff2;
                    int sqDist1 = xDiff1*xDiff1 + zDiff1*zDiff1;
                    return sqDist2 - sqDist1;
                }
            });
            lastPlayerChunk = playerChunk;
        }

        if(sortedChunkList != null) {
            long renderStartNS = System.nanoTime();
            modelBatch.begin(VoxelGameClient.getInstance().getCamera());
            float playerX = VoxelGameClient.getInstance().getPlayer().getPosition().getX(),
                    playerY = VoxelGameClient.getInstance().getPlayer().getPosition().getY(),
                    playerZ = VoxelGameClient.getInstance().getPlayer().getPosition().getZ();
            skybox.transform.translate(playerX, playerY, playerZ);
            modelBatch.render(skybox);
            skybox.transform.translate(-playerX, -playerY, -playerZ);
            if(VoxelGameClient.getInstance().isWireframe())
                Gdx.gl.glLineWidth(5);
            boolean[] chunkVisible = new boolean[sortedChunkList.size()];
            int chunkNum = 0;
            for (IChunk c : sortedChunkList) {
                int x = c.getStartPosition().x;
                int z = c.getStartPosition().z;
                int halfWidth = getChunkSize()/2;
                int midX = x + halfWidth;
                int midZ = z + halfWidth;
                int midY = c.getHighestPoint()/2;
                boolean visible = VoxelGameClient.getInstance().getGameRenderer().getFrustum().boundsInFrustum(midX, midY, midZ, halfWidth, midY, halfWidth);
                chunkVisible[chunkNum] = visible;
                chunkNum++;
                if(visible) {
                    c.render(modelBatch);
                }
            }
            chunkNum = 0;
            for (IChunk c : sortedChunkList) {
                if(chunkVisible[chunkNum]) {
                    c.renderTranslucent(modelBatch);
                }
                chunkNum++;
            }
            modelBatch.end();
            if (VoxelGameClient.getInstance().numChunkRenders == 100) {  // Reset every 100 renders
                VoxelGameClient.getInstance().numChunkRenders = 0;
                VoxelGameClient.getInstance().chunkRenderTimes = 0;
            }
            VoxelGameClient.getInstance().chunkRenderTimes += (int) (System.nanoTime() - renderStartNS);
            VoxelGameClient.getInstance().numChunkRenders++;
        }
    }

    @Override
    public void loadChunks(EntityPosition playerPosition, int viewDistance) {
        if (!remote) { //don't gen chunks if we're not local
            this.getChunksInRange(playerPosition, viewDistance);
        }

    }

    @Override
    public int getSeaLevel() {
        return SEA_LEVEL;
    }

    @Override
    public int getChunkPosition(float value) {
        int subtraction = MathUtils.floor(value) & (CHUNK_SIZE-1);
        return MathUtils.floor(value - subtraction);
    }

    @Override
    public float getGravity() {
        return GRAVITY;
    }

    @Override
    public float applyGravity(float velocity, long ms) {
        if (ms < 0) ms = 0 - ms;
        return Math.max(-TERMINAL_VELOCITY, velocity - (getGravity() / 1000) * (ms / 10f));
    }

    @Override
    public void removeBlock(int x, int y, int z) {
        synchronized (this) {
            final IChunk c = this.getChunkAtPosition(x, z);
            if (c != null) {
                x &= getChunkSize()-1;
                y &= getChunkSize()-1;
                c.removeBlock(x, y, z);
                c.setMeta((short) 0, x, y, z);
            }
        }
    }

    @Override
    public void addBlock(int block, int x, int y, int z) {
        synchronized (this) {
            final IChunk c = this.getChunkAtPosition(x, z);
            if(c != null)
                c.setBlock(block, x & (getChunkSize() - 1), y, z * (getChunkSize() - 1));
        }
    }

    @Override
    public IChunk[] getChunksInRange(EntityPosition epos, int viewDistance) {
        List<IChunk> chunkList = new ArrayList<>();
        int playerChunkX = getChunkPosition(epos.getX());
        int playerChunkZ = getChunkPosition(epos.getZ());
        int range = viewDistance * CHUNK_SIZE;
        for (int x = playerChunkX - range; x < playerChunkX + range; x += CHUNK_SIZE) {
            for (int z = playerChunkZ - range; z < playerChunkZ + range; z += CHUNK_SIZE) {
                chunkList.add(loadChunk(x, z));
            }
        }
        return chunkList.toArray(new IChunk[chunkList.size()]);
    }

    @Override
    public void addChunk(final IChunk chunk) {
        IChunk c = getChunkAtPosition(chunk.getStartPosition());
        if (c != null) {
            removeChunkFromMap(chunk.getStartPosition());
            this.chunkList.remove(c);
        }
        addChunk(chunk, chunk.getStartPosition().x, chunk.getStartPosition().z);

        addSun(chunk);
    }

    @Override
    public List<Entity> getLoadedEntities() {
        return this.loadedEntities;
    }

    private void addChunk(IChunk chunk, int x, int z) {
        IntMap<IChunk> foundChunkMapZ = this.chunkMapX.get(x);
        if(foundChunkMapZ == null) {
            foundChunkMapZ = new IntMap<>();
            this.chunkMapX.put(x, foundChunkMapZ);
        }
        foundChunkMapZ.put(z, chunk);
        this.chunkList.add(chunk);
    }

    private IChunk loadChunk(int startX, int startZ) {
        IChunk foundChunk = getChunkAtPosition(startX, startZ);
        if (foundChunk == null && !remote) {
            final IChunk c = new Chunk(this, new Vec3i(startX, 0, startZ), VoxelGameAPI.instance.getBiomeByID(0));
            VoxelGameAPI.instance.getEventManager().push(new EventFinishChunkGen(c));
            addChunk(c, startX, startZ);
            addSun(c);
            return c;
        } else {
            return foundChunk;
        }
    }

    private void addSun(IChunk c) {
        for (int x = 0; x < CHUNK_SIZE; x++) {
            for (int z = 0; z < CHUNK_SIZE; z++) {
                c.setSunlight(x, WORLD_HEIGHT - 1, z, 16);
                addToSunlightQueue(c.getStartPosition().x + x, WORLD_HEIGHT - 1, c.getStartPosition().z + z);
            }
        }
        c.finishChangingSunlight();
    }

    public void addEntity(Entity e) {
        loadedEntities.add(e);
    }


    @Override
    public void rerenderChunk(IChunk c) {
        if(c == null)
            return;
        chunksToRerender.add(c);
    }

    @Override
    public ChunkGenerator getChunkGen() {
        return this.chunkGen;
    }

    /**
     * Add a block to a list of blocks to process sunlight for
     * The block at the position passed should be translucent or null and have a sunlight level greater than 0
     */
    @Override
    public void addToSunlightQueue(int x, int y, int z) {
        sunlightQueue.add(new int[]{x, y, z});
    }

    @Override
    public void addToSunlightRemovalQueue(int x, int y, int z) {
        sunlightRemovalQueue.add(new int[]{x, y, z});
    }

    @Override
    public void processLightQueue() {
        if (sunlightQueue.isEmpty() && sunlightRemovalQueue.isEmpty())
            return;
        shouldUpdateLight = true;
        if (lightUpdaters < 2) {
            lightUpdaters++;
            new Thread("Light update") {
                @Override
                public void run() {
                    shouldUpdateLight = false;

                    processLightRemovalQueue();

                    Queue<IChunk> changedChunks = new LinkedBlockingDeque<>();
                    int[] pos;
                    while ((pos = sunlightQueue.poll()) != null) {
                        int x = pos[0];
                        int cx = x & (CHUNK_SIZE-1);
                        int y = pos[1];
                        int z = pos[2];
                        int cz = z & (CHUNK_SIZE-1);
                        IChunk posChunk = getChunkAtPosition(x, z);
                        if (posChunk == null) {
                            continue;
                        }
                        int ll = posChunk.getSunlight(cx, y, cz);

                        // Spread off to each side
                        for(Side s : Side.values()) {
                            int nextLL = ll - 1; // Decayed light level for the spread
                            int sx = x; // Side x coord
                            int sy = y; // Side y coord
                            int sz = z; // Side z coord
                            int scx = cx; // Chunk-relative side x coord
                            int scz = cz; // Chunk-relative side z coord
                            IChunk sChunk = posChunk;

                            // Offset values based on side
                            switch(s) {
                                case TOP:
                                    sy += 1;
                                    break;
                                case BOTTOM:
                                    sy -= 1;
                                    break;
                                case WEST:
                                    sx -= 1;
                                    scx -= 1;
                                    break;
                                case EAST:
                                    sx += 1;
                                    scx += 1;
                                    break;
                                case NORTH:
                                    sz += 1;
                                    scz += 1;
                                    break;
                                case SOUTH:
                                    sz -= 1;
                                    scz -= 1;
                                    break;
                            }
                            if(sy < 0)
                                continue;
                            if(sy > WORLD_HEIGHT-1)
                                continue;

                            // Select the correct chunk
                            if(scz < 0) {
                                scz += CHUNK_SIZE;
                                sChunk = getChunkAtPosition(sx, sz);
                            } else if(scz > CHUNK_SIZE-1) {
                                scz -= CHUNK_SIZE;
                                sChunk = getChunkAtPosition(sx, sz);
                            }
                            if(scx < 0) {
                                scx += CHUNK_SIZE;
                                sChunk = getChunkAtPosition(sx, sz);
                            } else if(scx > CHUNK_SIZE-1) {
                                scx -= CHUNK_SIZE;
                                sChunk = getChunkAtPosition(sx, sz);
                            }

                            if(sChunk == null)
                                continue;

                            // Spread lighting
                            Block sBlock = sChunk.getBlock(scx, sy, scz);
                            // When spreading down, lighting at max level does not decay
                            if(s == Side.BOTTOM) {
                                Block block = posChunk.getBlock(cx, y, cz); // Block being spread from
                                if (ll == 16 && (block == null || block.decreasesLight()))
                                    nextLL = 16;
                            }
                            if(sBlock == null || sBlock.doesLightPassThrough() || !sBlock.decreasesLight()) {
                                if(sChunk.getSunlight(scx, sy, scz) < nextLL) {
                                    sChunk.setSunlight(scx, sy, scz, nextLL);
                                    addToSunlightQueue(sx, sy, sz);
                                    changedChunks.add(sChunk);
                                }
                            }
{'fraction_non_alphanumeric': 0.10010819583446037, 'fraction_numerical': 0.0062212604814714636, 'mean_word_length': 1.5720745791011548, 'pattern_counts': {'":': 0, '<': 79, '<?xml version=': 0, '>': 63, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 55, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4656972', 'n_tokens_mistral': 10461, 'n_tokens_neox': 9939, 'n_words': 2216}