package net.minecraft.client.renderer.chunk;

import it.unimi.dsi.fastutil.objects.ObjectArraySet;
import com.google.common.primitives.Doubles;
import java.util.concurrent.atomic.AtomicBoolean;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import java.util.Iterator;
import net.minecraft.client.renderer.block.BlockRenderDispatcher;
import java.util.function.Consumer;
import java.util.function.Function;
import net.minecraft.world.level.block.RenderShape;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.BlockGetter;
import java.util.Random;
import net.minecraft.client.renderer.block.ModelBlockRenderer;
import com.mojang.blaze3d.vertex.PoseStack;
import java.util.concurrent.CancellationException;
import java.util.Collection;
import net.minecraft.client.Camera;
import net.minecraft.core.Vec3i;
import net.minecraft.core.Direction;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.Util;
import java.util.stream.Collectors;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.google.common.collect.Sets;
import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.AABB;
import java.util.Map;
import net.minecraft.world.level.block.entity.BlockEntity;
import java.util.Set;
import javax.annotation.Nullable;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.LogManager;
import java.util.concurrent.CompletionStage;
import com.mojang.blaze3d.vertex.VertexBuffer;
import com.mojang.blaze3d.vertex.BufferBuilder;
import net.minecraft.client.Minecraft;
import net.minecraft.CrashReport;
import java.util.concurrent.CompletableFuture;
import java.util.List;
import com.google.common.collect.Lists;
import net.minecraft.client.renderer.RenderType;
import com.google.common.collect.Queues;
import net.minecraft.world.phys.Vec3;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.world.level.Level;
import java.util.concurrent.Executor;
import net.minecraft.util.thread.ProcessorMailbox;
import net.minecraft.client.renderer.ChunkBufferBuilderPack;
import java.util.Queue;
import java.util.PriorityQueue;
import org.apache.logging.log4j.Logger;

public class ChunkRenderDispatcher {
    private static final Logger LOGGER;
    private final PriorityQueue<RenderChunk.ChunkCompileTask> toBatch;
    private final Queue<ChunkBufferBuilderPack> freeBuffers;
    private final Queue<Runnable> toUpload;
    private volatile int toBatchCount;
    private volatile int freeBufferCount;
    private final ChunkBufferBuilderPack fixedBuffers;
    private final ProcessorMailbox<Runnable> mailbox;
    private final Executor executor;
    private Level level;
    private final LevelRenderer renderer;
    private Vec3 camera;
    
    public ChunkRenderDispatcher(final Level bjw, final LevelRenderer dqr, final Executor executor, final boolean boolean4, final ChunkBufferBuilderPack dqi) {
        this.toBatch = Queues.<RenderChunk.ChunkCompileTask>newPriorityQueue();
        this.toUpload = Queues.newConcurrentLinkedQueue();
        this.camera = Vec3.ZERO;
        this.level = bjw;
        this.renderer = dqr;
        final int integer7 = Math.max(1, (int)(Runtime.getRuntime().maxMemory() * 0.3) / (RenderType.chunkBufferLayers().stream().mapToInt(RenderType::bufferSize).sum() * 4) - 1);
        final int integer8 = Runtime.getRuntime().availableProcessors();
        final int integer9 = boolean4 ? integer8 : Math.min(integer8, 4);
        final int integer10 = Math.max(1, Math.min(integer9, integer7));
        this.fixedBuffers = dqi;
        final List<ChunkBufferBuilderPack> list11 = Lists.newArrayListWithExpectedSize(integer10);
        try {
            for (int integer11 = 0; integer11 < integer10; ++integer11) {
                list11.add(new ChunkBufferBuilderPack());
            }
        }
        catch (OutOfMemoryError outOfMemoryError12) {
            ChunkRenderDispatcher.LOGGER.warn("Allocated only {}/{} buffers", list11.size(), integer10);
            for (int integer12 = Math.min(list11.size() * 2 / 3, list11.size() - 1), integer13 = 0; integer13 < integer12; ++integer13) {
                list11.remove(list11.size() - 1);
            }
            System.gc();
        }
        this.freeBuffers = Queues.newArrayDeque(list11);
        this.freeBufferCount = this.freeBuffers.size();
        this.executor = executor;
        (this.mailbox = ProcessorMailbox.create(executor, "Chunk Renderer")).tell(this::runTask);
    }
    
    public void setLevel(final Level bjw) {
        this.level = bjw;
    }
    
    private void runTask() {
        if (this.freeBuffers.isEmpty()) {
            return;
        }
        final RenderChunk.ChunkCompileTask a2 = this.toBatch.poll();
        if (a2 == null) {
            return;
        }
        final ChunkBufferBuilderPack dqi3 = this.freeBuffers.poll();
        this.toBatchCount = this.toBatch.size();
        this.freeBufferCount = this.freeBuffers.size();
        CrashReport h5;
        final ChunkBufferBuilderPack chunkBufferBuilderPack;
        CompletableFuture.runAsync(() -> {}, this.executor).thenCompose(void3 -> a2.doTask(dqi3)).whenComplete((a, throwable) -> {
            if (throwable != null) {
                h5 = CrashReport.forThrowable(throwable, "Batching chunks");
                Minecraft.getInstance().delayCrash(Minecraft.getInstance().fillReport(h5));
            }
            else {
                this.mailbox.tell(() -> {
                    if (a == ChunkTaskResult.SUCCESSFUL) {
                        chunkBufferBuilderPack.clearAll();
                    }
                    else {
                        chunkBufferBuilderPack.discardAll();
                    }
                    this.freeBuffers.add(chunkBufferBuilderPack);
                    this.freeBufferCount = this.freeBuffers.size();
                    this.runTask();
                });
            }
        });
    }
    
    public String getStats() {
        return String.format("pC: %03d, pU: %02d, aB: %02d", this.toBatchCount, this.toUpload.size(), this.freeBufferCount);
    }
    
    public void setCamera(final Vec3 cvl) {
        this.camera = cvl;
    }
    
    public Vec3 getCameraPosition() {
        return this.camera;
    }
    
    public boolean uploadAllPendingUploads() {
        boolean boolean2 = false;
        Runnable runnable3;
        while ((runnable3 = this.toUpload.poll()) != null) {
            runnable3.run();
            boolean2 = true;
        }
        return boolean2;
    }
    
    public void rebuildChunkSync(final RenderChunk c) {
        c.compileSync();
    }
    
    public void blockUntilClear() {
        this.clearBatchQueue();
    }
    
    public void schedule(final RenderChunk.ChunkCompileTask a) {
        this.mailbox.tell(() -> {
            this.toBatch.offer(a);
            this.toBatchCount = this.toBatch.size();
            this.runTask();
        });
    }
    
    public CompletableFuture<Void> uploadChunkLayer(final BufferBuilder cyg, final VertexBuffer cyn) {
        return CompletableFuture.runAsync(() -> {}, this.toUpload::add).<Void>thenCompose(void3 -> this.doUploadChunkLayer(cyg, cyn));
    }
    
    private CompletableFuture<Void> doUploadChunkLayer(final BufferBuilder cyg, final VertexBuffer cyn) {
        return cyn.uploadLater(cyg);
    }
    
    private void clearBatchQueue() {
        while (!this.toBatch.isEmpty()) {
            final RenderChunk.ChunkCompileTask a2 = this.toBatch.poll();
            if (a2 != null) {
                a2.cancel();
            }
        }
        this.toBatchCount = 0;
    }
    
    public boolean isQueueEmpty() {
        return this.toBatchCount == 0 && this.toUpload.isEmpty();
    }
    
    public void dispose() {
        this.clearBatchQueue();
        this.mailbox.close();
        this.freeBuffers.clear();
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
    
    public class RenderChunk {
        public final AtomicReference<CompiledChunk> compiled;
        @Nullable
        private RebuildTask lastRebuildTask;
        @Nullable
        private ResortTransparencyTask lastResortTransparencyTask;
        private final Set<BlockEntity> globalBlockEntities;
        private final Map<RenderType, VertexBuffer> buffers;
        public AABB bb;
        private int lastFrame;
        private boolean dirty;
        private final BlockPos.MutableBlockPos origin;
        private final BlockPos.MutableBlockPos[] relativeOrigins;
        private boolean playerChanged;
        
        public RenderChunk() {
            this.compiled = new AtomicReference<CompiledChunk>(CompiledChunk.UNCOMPILED);
            this.globalBlockEntities = Sets.newHashSet();
            this.buffers = RenderType.chunkBufferLayers().stream().collect(Collectors.toMap(drb -> drb, drb -> new VertexBuffer(DefaultVertexFormat.BLOCK)));
            this.lastFrame = -1;
            this.dirty = true;
            this.origin = new BlockPos.MutableBlockPos(-1, -1, -1);
            int integer2;
            this.relativeOrigins = Util.<BlockPos.MutableBlockPos[]>make(new BlockPos.MutableBlockPos[6], arr -> {
                for (integer2 = 0; integer2 < arr.length; ++integer2) {
                    arr[integer2] = new BlockPos.MutableBlockPos();
                }
            });
        }
        
        private boolean doesChunkExistAt(final BlockPos fk) {
            return ChunkRenderDispatcher.this.level.getChunk(fk.getX() >> 4, fk.getZ() >> 4, ChunkStatus.FULL, false) != null;
        }
        
        public boolean hasAllNeighbors() {
            final int integer2 = 24;
            return this.getDistToPlayerSqr() <= 576.0 || (this.doesChunkExistAt(this.relativeOrigins[Direction.WEST.ordinal()]) && this.doesChunkExistAt(this.relativeOrigins[Direction.NORTH.ordinal()]) && this.doesChunkExistAt(this.relativeOrigins[Direction.EAST.ordinal()]) && this.doesChunkExistAt(this.relativeOrigins[Direction.SOUTH.ordinal()]));
        }
        
        public boolean setFrame(final int integer) {
            if (this.lastFrame == integer) {
                return false;
            }
            this.lastFrame = integer;
            return true;
        }
        
        public VertexBuffer getBuffer(final RenderType drb) {
            return this.buffers.get(drb);
        }
        
        public void setOrigin(final int integer1, final int integer2, final int integer3) {
            if (integer1 == this.origin.getX() && integer2 == this.origin.getY() && integer3 == this.origin.getZ()) {
                return;
            }
            this.reset();
            this.origin.set(integer1, integer2, integer3);
            this.bb = new AABB(integer1, integer2, integer3, integer1 + 16, integer2 + 16, integer3 + 16);
            for (final Direction fp8 : Direction.values()) {
                this.relativeOrigins[fp8.ordinal()].set(this.origin).move(fp8, 16);
            }
        }
        
        protected double getDistToPlayerSqr() {
            final Camera dbb2 = Minecraft.getInstance().gameRenderer.getMainCamera();
            final double double3 = this.bb.minX + 8.0 - dbb2.getPosition().x;
            final double double4 = this.bb.minY + 8.0 - dbb2.getPosition().y;
            final double double5 = this.bb.minZ + 8.0 - dbb2.getPosition().z;
            return double3 * double3 + double4 * double4 + double5 * double5;
        }
        
        private void beginLayer(final BufferBuilder cyg) {
            cyg.begin(7, DefaultVertexFormat.BLOCK);
        }
        
        public CompiledChunk getCompiledChunk() {
            return this.compiled.get();
        }
        
        private void reset() {
            this.cancelTasks();
            this.compiled.set(CompiledChunk.UNCOMPILED);
            this.dirty = true;
        }
        
        public void releaseBuffers() {
            this.reset();
            this.buffers.values().forEach(VertexBuffer::close);
        }
        
        public BlockPos getOrigin() {
            return this.origin;
        }
        
        public void setDirty(final boolean boolean1) {
            final boolean boolean2 = this.dirty;
            this.dirty = true;
            this.playerChanged = (boolean1 | (boolean2 && this.playerChanged));
        }
        
        public void setNotDirty() {
            this.dirty = false;
            this.playerChanged = false;
        }
        
        public boolean isDirty() {
            return this.dirty;
        }
        
        public boolean isDirtyFromPlayer() {
            return this.dirty && this.playerChanged;
        }
        
        public BlockPos getRelativeOrigin(final Direction fp) {
            return this.relativeOrigins[fp.ordinal()];
        }
        
        public boolean resortTransparency(final RenderType drb, final ChunkRenderDispatcher dth) {
            final CompiledChunk b4 = this.getCompiledChunk();
            if (this.lastResortTransparencyTask != null) {
                this.lastResortTransparencyTask.cancel();
            }
            if (!b4.hasLayer.contains(drb)) {
                return false;
            }
            dth.schedule(this.lastResortTransparencyTask = new ResortTransparencyTask(this.getDistToPlayerSqr(), b4));
            return true;
        }
        
        protected void cancelTasks() {
            if (this.lastRebuildTask != null) {
                this.lastRebuildTask.cancel();
                this.lastRebuildTask = null;
            }
            if (this.lastResortTransparencyTask != null) {
                this.lastResortTransparencyTask.cancel();
                this.lastResortTransparencyTask = null;
            }
        }
        
        public ChunkCompileTask createCompileTask() {
            this.cancelTasks();
            final BlockPos fk2 = this.origin.immutable();
            final int integer3 = 1;
            final RenderChunkRegion dti4 = RenderChunkRegion.createIfNotEmpty(ChunkRenderDispatcher.this.level, fk2.offset(-1, -1, -1), fk2.offset(16, 16, 16), 1);
            return this.lastRebuildTask = new RebuildTask(this.getDistToPlayerSqr(), dti4);
        }
        
        public void rebuildChunkAsync(final ChunkRenderDispatcher dth) {
            final ChunkCompileTask a3 = this.createCompileTask();
            dth.schedule(a3);
        }
        
        private void updateGlobalBlockEntities(final Set<BlockEntity> set) {
            final Set<BlockEntity> set2 = Sets.newHashSet(set);
            final Set<BlockEntity> set3 = Sets.newHashSet(this.globalBlockEntities);
            set2.removeAll(this.globalBlockEntities);
            set3.removeAll(set);
            this.globalBlockEntities.clear();
            this.globalBlockEntities.addAll(set);
            ChunkRenderDispatcher.this.renderer.updateGlobalBlockEntities(set3, set2);
        }
        
        public void compileSync() {
            final ChunkCompileTask a2 = this.createCompileTask();
            a2.doTask(ChunkRenderDispatcher.this.fixedBuffers);
        }
        
        class RebuildTask extends ChunkCompileTask {
            @Nullable
            protected RenderChunkRegion region;
            
            public RebuildTask(final double double2, @Nullable final RenderChunkRegion dti) {
                super(double2);
                this.region = dti;
            }
            
            @Override
            public CompletableFuture<ChunkTaskResult> doTask(final ChunkBufferBuilderPack dqi) {
                if (this.isCancelled.get()) {
                    return CompletableFuture.<ChunkTaskResult>completedFuture(ChunkTaskResult.CANCELLED);
                }
                if (!RenderChunk.this.hasAllNeighbors()) {
                    this.region = null;
                    RenderChunk.this.setDirty(false);
                    this.isCancelled.set(true);
                    return CompletableFuture.<ChunkTaskResult>completedFuture(ChunkTaskResult.CANCELLED);
                }
                if (this.isCancelled.get()) {
                    return CompletableFuture.<ChunkTaskResult>completedFuture(ChunkTaskResult.CANCELLED);
                }
                final Vec3 cvl3 = ChunkRenderDispatcher.this.getCameraPosition();
                final float float4 = (float)cvl3.x;
                final float float5 = (float)cvl3.y;
                final float float6 = (float)cvl3.z;
                final CompiledChunk b7 = new CompiledChunk();
                final Set<BlockEntity> set8 = this.compile(float4, float5, float6, b7, dqi);
                RenderChunk.this.updateGlobalBlockEntities(set8);
                if (this.isCancelled.get()) {
                    return CompletableFuture.<ChunkTaskResult>completedFuture(ChunkTaskResult.CANCELLED);
                }
                final List<CompletableFuture<Void>> list9 = Lists.newArrayList();
                b7.hasLayer.forEach(drb -> list9.add(ChunkRenderDispatcher.this.uploadChunkLayer(dqi.builder(drb), RenderChunk.this.getBuffer(drb))));
                final CompiledChunk newValue;
                return Util.sequence(list9).<ChunkTaskResult>handle((list, throwable) -> {
                    if (throwable != null && !(throwable instanceof CancellationException) && !(throwable instanceof InterruptedException)) {
                        Minecraft.getInstance().delayCrash(CrashReport.forThrowable(throwable, "Rendering chunk"));
                    }
                    if (this.isCancelled.get()) {
                        return ChunkTaskResult.CANCELLED;
                    }
                    else {
                        RenderChunk.this.compiled.set(newValue);
                        return ChunkTaskResult.SUCCESSFUL;
                    }
                });
            }
            
            private Set<BlockEntity> compile(final float float1, final float float2, final float float3, final CompiledChunk b, final ChunkBufferBuilderPack dqi) {
                final int integer7 = 1;
                final BlockPos fk8 = RenderChunk.this.origin.immutable();
                final BlockPos fk9 = fk8.offset(15, 15, 15);
                final VisGraph dtj10 = new VisGraph();
                final Set<BlockEntity> set11 = Sets.newHashSet();
                final RenderChunkRegion dti12 = this.region;
                this.region = null;
                final PoseStack cyl13 = new PoseStack();
                if (dti12 != null) {
                    ModelBlockRenderer.enableCaching();
                    final Random random14 = new Random();
                    final BlockRenderDispatcher drk15 = Minecraft.getInstance().getBlockRenderer();
                    for (final BlockPos fk10 : BlockPos.betweenClosed(fk8, fk9)) {
                        final BlockState byj18 = dti12.getBlockState(fk10);
                        final Block bph19 = byj18.getBlock();
                        if (byj18.isSolidRender(dti12, fk10)) {
                            dtj10.setOpaque(fk10);
                        }
                        if (bph19.isEntityBlock()) {
                            final BlockEntity bwl20 = dti12.getBlockEntity(fk10, LevelChunk.EntityCreationType.CHECK);
                            if (bwl20 != null) {
                                this.<BlockEntity>handleBlockEntity(b, set11, bwl20);
                            }
                        }
                        final FluidState coj20 = dti12.getFluidState(fk10);
                        if (!coj20.isEmpty()) {
                            final RenderType drb21 = ItemBlockRenderTypes.getRenderLayer(coj20);
                            final BufferBuilder cyg22 = dqi.builder(drb21);
                            if (b.hasLayer.add(drb21)) {
                                RenderChunk.this.beginLayer(cyg22);
                            }
                            if (drk15.renderLiquid(fk10, dti12, cyg22, coj20)) {
                                b.isCompletelyEmpty = false;
                                b.hasBlocks.add(drb21);
                            }
                        }
                        if (byj18.getRenderShape() != RenderShape.INVISIBLE) {
                            final RenderType drb21 = ItemBlockRenderTypes.getChunkRenderType(byj18);
                            final BufferBuilder cyg22 = dqi.builder(drb21);
                            if (b.hasLayer.add(drb21)) {
                                RenderChunk.this.beginLayer(cyg22);
                            }
                            cyl13.pushPose();
                            cyl13.translate(fk10.getX() & 0xF, fk10.getY() & 0xF, fk10.getZ() & 0xF);
                            if (drk15.renderBatched(byj18, fk10, dti12, cyl13, cyg22, true, random14)) {
                                b.isCompletelyEmpty = false;
                                b.hasBlocks.add(drb21);
                            }
                            cyl13.popPose();
                        }
                    }
                    if (b.hasBlocks.contains(RenderType.translucent())) {
                        final BufferBuilder cyg23 = dqi.builder(RenderType.translucent());
                        cyg23.sortQuads(float1 - fk8.getX(), float2 - fk8.getY(), float3 - fk8.getZ());
                        b.transparencyState = cyg23.getState();
                    }
                    b.hasLayer.stream().map(dqi::builder).forEach(BufferBuilder::end);
                    ModelBlockRenderer.clearCache();
                }
                b.visibilitySet = dtj10.resolve();
                return set11;
            }
            
            private <E extends BlockEntity> void handleBlockEntity(final CompiledChunk b, final Set<BlockEntity> set, final E bwl) {
                final BlockEntityRenderer<E> dsr5 = BlockEntityRenderDispatcher.instance.<E>getRenderer(bwl);
                if (dsr5 != null) {
                    b.renderableBlockEntities.add(bwl);
                    if (dsr5.shouldRenderOffScreen(bwl)) {
                        set.add(bwl);
                    }
                }
            }
            
            @Override
            public void cancel() {
                this.region = null;
                if (this.isCancelled.compareAndSet(false, true)) {
                    RenderChunk.this.setDirty(false);
                }
            }
        }
        
        class ResortTransparencyTask extends ChunkCompileTask {
            private final CompiledChunk compiledChunk;
            
            public ResortTransparencyTask(final double double2, final CompiledChunk b) {
                super(double2);
                this.compiledChunk = b;
            }
            
            @Override
            public CompletableFuture<ChunkTaskResult> doTask(final ChunkBufferBuilderPack dqi) {
                if (this.isCancelled.get()) {
                    return CompletableFuture.<ChunkTaskResult>completedFuture(ChunkTaskResult.CANCELLED);
                }
                if (!RenderChunk.this.hasAllNeighbors()) {
                    this.isCancelled.set(true);
                    return CompletableFuture.<ChunkTaskResult>completedFuture(ChunkTaskResult.CANCELLED);
                }
                if (this.isCancelled.get()) {
                    return CompletableFuture.<ChunkTaskResult>completedFuture(ChunkTaskResult.CANCELLED);
                }
                final Vec3 cvl3 = ChunkRenderDispatcher.this.getCameraPosition();
                final float float4 = (float)cvl3.x;
                final float float5 = (float)cvl3.y;
                final float float6 = (float)cvl3.z;
                final BufferBuilder.State b7 = this.compiledChunk.transparencyState;
                if (b7 == null || !this.compiledChunk.hasBlocks.contains(RenderType.translucent())) {
                    return CompletableFuture.<ChunkTaskResult>completedFuture(ChunkTaskResult.CANCELLED);
                }
                final BufferBuilder cyg8 = dqi.builder(RenderType.translucent());
                RenderChunk.this.beginLayer(cyg8);
                cyg8.restoreState(b7);
                cyg8.sortQuads(float4 - RenderChunk.this.origin.getX(), float5 - RenderChunk.this.origin.getY(), float6 - RenderChunk.this.origin.getZ());
                this.compiledChunk.transparencyState = cyg8.getState();
                cyg8.end();
                if (this.isCancelled.get()) {
                    return CompletableFuture.<ChunkTaskResult>completedFuture(ChunkTaskResult.CANCELLED);
                }
                final CompletableFuture<ChunkTaskResult> completableFuture9 = ChunkRenderDispatcher.this.uploadChunkLayer(dqi.builder(RenderType.translucent()), RenderChunk.this.getBuffer(RenderType.translucent())).<ChunkTaskResult>thenApply(void1 -> ChunkTaskResult.CANCELLED);
                return completableFuture9.<ChunkTaskResult>handle((a, throwable) -> {
                    if (throwable != null && !(throwable instanceof CancellationException) && !(throwable instanceof InterruptedException)) {
                        Minecraft.getInstance().delayCrash(CrashReport.forThrowable(throwable, "Rendering chunk"));
                    }
                    return this.isCancelled.get() ? ChunkTaskResult.CANCELLED : ChunkTaskResult.SUCCESSFUL;
                });
            }
            
            @Override
            public void cancel() {
                this.isCancelled.set(true);
            }
        }
        
        abstract class ChunkCompileTask implements Comparable<ChunkCompileTask> {
            protected final double distAtCreation;
            protected final AtomicBoolean isCancelled;
            
            public ChunkCompileTask(final double double2) {
                this.isCancelled = new AtomicBoolean(false);
                this.distAtCreation = double2;
            }
            
            public abstract CompletableFuture<ChunkTaskResult> doTask(final ChunkBufferBuilderPack dqi);
            
            public abstract void cancel();
            
            @Override
            public int compareTo(final ChunkCompileTask a) {
                return Doubles.compare(this.distAtCreation, a.distAtCreation);
            }
        }
    }
    
    enum ChunkTaskResult {
        SUCCESSFUL, 
        CANCELLED;
    }
    
    public static class CompiledChunk {
        public static final CompiledChunk UNCOMPILED;
        private final Set<RenderType> hasBlocks;
        private final Set<RenderType> hasLayer;
        private boolean isCompletelyEmpty;
        private final List<BlockEntity> renderableBlockEntities;
        private VisibilitySet visibilitySet;
        @Nullable
        private BufferBuilder.State transparencyState;
        
        public CompiledChunk() {
            this.hasBlocks = (Set<RenderType>)new ObjectArraySet();
            this.hasLayer = (Set<RenderType>)new ObjectArraySet();
            this.isCompletelyEmpty = true;
            this.renderableBlockEntities = Lists.newArrayList();
            this.visibilitySet = new VisibilitySet();
        }
        
        public boolean hasNoRenderableLayers() {
            return this.isCompletelyEmpty;
        }
        
        public boolean isEmpty(final RenderType drb) {
            return !this.hasBlocks.contains(drb);
        }
        
        public List<BlockEntity> getRenderableBlockEntities() {
            return this.renderableBlockEntities;
        }
        
        public boolean facesCanSeeEachother(final Direction fp1, final Direction fp2) {
            return this.visibilitySet.visibilityBetween(fp1, fp2);
        }
        
        static {
            UNCOMPILED = new CompiledChunk() {
                @Override
                public boolean facesCanSeeEachother(final Direction fp1, final Direction fp2) {
                    return false;
                }
            };
        }
    }
}
