ClientChunkCache.java

net.minecraft.client.multiplayer.ClientChunkCache

信息

  • 全限定名:net.minecraft.client.multiplayer.ClientChunkCache
  • 类型:public class
  • 包:net.minecraft.client.multiplayer
  • 源码路径:src/main/java/net/minecraft/client/multiplayer/ClientChunkCache.java
  • 起始行号:L35
  • 继承:ChunkSource
  • 职责:

    TODO

字段/常量

  • LOGGER

    • 类型: Logger
    • 修饰符: private static final
    • 源码定位: L36
    • 说明:

      TODO

  • emptyChunk

    • 类型: LevelChunk
    • 修饰符: private final
    • 源码定位: L37
    • 说明:

      TODO

  • lightEngine

    • 类型: LevelLightEngine
    • 修饰符: private final
    • 源码定位: L38
    • 说明:

      TODO

  • storage

    • 类型: ClientChunkCache.Storage
    • 修饰符: private volatile
    • 源码定位: L39
    • 说明:

      TODO

  • level

    • 类型: ClientLevel
    • 修饰符: private final
    • 源码定位: L40
    • 说明:

      TODO

内部类/嵌套类型

  • net.minecraft.client.multiplayer.ClientChunkCache.Storage
    • 类型: class
    • 修饰符: private final
    • 源码定位: L191
    • 说明:

      TODO

构造器

public ClientChunkCache(ClientLevel level, int serverChunkRadius) @ L42

  • 构造器名:ClientChunkCache
  • 源码定位:L42
  • 修饰符:public

参数:

  • level: ClientLevel
  • serverChunkRadius: int

说明:

TODO

方法

下面的方法块按源码顺序生成。

public LevelLightEngine getLightEngine() @ L49

  • 方法名:getLightEngine
  • 源码定位:L49
  • 返回类型:LevelLightEngine
  • 修饰符:public

参数:

说明:

TODO

private static boolean isValidChunk(LevelChunk chunk, int x, int z) @ L54

  • 方法名:isValidChunk
  • 源码定位:L54
  • 返回类型:boolean
  • 修饰符:private static

参数:

  • chunk: LevelChunk
  • x: int
  • z: int

说明:

TODO

public void drop(ChunkPos pos) @ L63

  • 方法名:drop
  • 源码定位:L63
  • 返回类型:void
  • 修饰符:public

参数:

  • pos: ChunkPos

说明:

TODO

public LevelChunk getChunk(int x, int z, ChunkStatus targetStatus, boolean loadOrGenerate) @ L73

  • 方法名:getChunk
  • 源码定位:L73
  • 返回类型:LevelChunk
  • 修饰符:public

参数:

  • x: int
  • z: int
  • targetStatus: ChunkStatus
  • loadOrGenerate: boolean

说明:

TODO

public BlockGetter getLevel() @ L84

  • 方法名:getLevel
  • 源码定位:L84
  • 返回类型:BlockGetter
  • 修饰符:public

参数:

说明:

TODO

public void replaceBiomes(int chunkX, int chunkZ, FriendlyByteBuf readBuffer) @ L89

  • 方法名:replaceBiomes
  • 源码定位:L89
  • 返回类型:void
  • 修饰符:public

参数:

  • chunkX: int
  • chunkZ: int
  • readBuffer: FriendlyByteBuf

说明:

TODO

public LevelChunk replaceWithPacketData(int chunkX, int chunkZ, FriendlyByteBuf readBuffer, Map<Heightmap.Types,long[]> heightmaps, Consumer<ClientboundLevelChunkPacketData.BlockEntityTagOutput> blockEntities) @ L103

  • 方法名:replaceWithPacketData
  • 源码定位:L103
  • 返回类型:LevelChunk
  • 修饰符:public

参数:

  • chunkX: int
  • chunkZ: int
  • readBuffer: FriendlyByteBuf
  • heightmaps: Map<Heightmap.Types,long[]>
  • blockEntities: Consumer<ClientboundLevelChunkPacketData.BlockEntityTagOutput>

说明:

TODO

public void tick(BooleanSupplier haveTime, boolean tickChunks) @ L131

  • 方法名:tick
  • 源码定位:L131
  • 返回类型:void
  • 修饰符:public

参数:

  • haveTime: BooleanSupplier
  • tickChunks: boolean

说明:

TODO

public void updateViewCenter(int x, int z) @ L135

  • 方法名:updateViewCenter
  • 源码定位:L135
  • 返回类型:void
  • 修饰符:public

参数:

  • x: int
  • z: int

说明:

TODO

public void updateViewRadius(int viewRange) @ L140

  • 方法名:updateViewRadius
  • 源码定位:L140
  • 返回类型:void
  • 修饰符:public

参数:

  • viewRange: int

说明:

TODO

private static int calculateStorageRange(int viewRange) @ L162

  • 方法名:calculateStorageRange
  • 源码定位:L162
  • 返回类型:int
  • 修饰符:private static

参数:

  • viewRange: int

说明:

TODO

public String gatherStats() @ L166

  • 方法名:gatherStats
  • 源码定位:L166
  • 返回类型:String
  • 修饰符:public

参数:

说明:

TODO

public int getLoadedChunksCount() @ L171

  • 方法名:getLoadedChunksCount
  • 源码定位:L171
  • 返回类型:int
  • 修饰符:public

参数:

说明:

TODO

public void onLightUpdate(LightLayer layer, SectionPos pos) @ L176

  • 方法名:onLightUpdate
  • 源码定位:L176
  • 返回类型:void
  • 修饰符:public

参数:

  • layer: LightLayer
  • pos: SectionPos

说明:

TODO

public LongOpenHashSet getLoadedEmptySections() @ L181

  • 方法名:getLoadedEmptySections
  • 源码定位:L181
  • 返回类型:LongOpenHashSet
  • 修饰符:public

参数:

说明:

TODO

public void onSectionEmptinessChanged(int sectionX, int sectionY, int sectionZ, boolean empty) @ L185

  • 方法名:onSectionEmptinessChanged
  • 源码定位:L185
  • 返回类型:void
  • 修饰符:public

参数:

  • sectionX: int
  • sectionY: int
  • sectionZ: int
  • empty: boolean

说明:

TODO

代码

@OnlyIn(Dist.CLIENT)
public class ClientChunkCache extends ChunkSource {
    private static final Logger LOGGER = LogUtils.getLogger();
    private final LevelChunk emptyChunk;
    private final LevelLightEngine lightEngine;
    private volatile ClientChunkCache.Storage storage;
    private final ClientLevel level;
 
    public ClientChunkCache(ClientLevel level, int serverChunkRadius) {
        this.level = level;
        this.emptyChunk = new EmptyLevelChunk(level, new ChunkPos(0, 0), level.registryAccess().lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.PLAINS));
        this.lightEngine = new LevelLightEngine(this, true, level.dimensionType().hasSkyLight());
        this.storage = new ClientChunkCache.Storage(calculateStorageRange(serverChunkRadius));
    }
 
    @Override
    public LevelLightEngine getLightEngine() {
        return this.lightEngine;
    }
 
    private static boolean isValidChunk(@Nullable LevelChunk chunk, int x, int z) {
        if (chunk == null) {
            return false;
        } else {
            ChunkPos pos = chunk.getPos();
            return pos.x() == x && pos.z() == z;
        }
    }
 
    public void drop(ChunkPos pos) {
        if (this.storage.inRange(pos.x(), pos.z())) {
            int index = this.storage.getIndex(pos.x(), pos.z());
            LevelChunk currentChunk = this.storage.getChunk(index);
            if (isValidChunk(currentChunk, pos.x(), pos.z())) {
                this.storage.drop(index, currentChunk);
            }
        }
    }
 
    public @Nullable LevelChunk getChunk(int x, int z, ChunkStatus targetStatus, boolean loadOrGenerate) {
        if (this.storage.inRange(x, z)) {
            LevelChunk chunk = this.storage.getChunk(this.storage.getIndex(x, z));
            if (isValidChunk(chunk, x, z)) {
                return chunk;
            }
        }
 
        return loadOrGenerate ? this.emptyChunk : null;
    }
 
    @Override
    public BlockGetter getLevel() {
        return this.level;
    }
 
    public void replaceBiomes(int chunkX, int chunkZ, FriendlyByteBuf readBuffer) {
        if (!this.storage.inRange(chunkX, chunkZ)) {
            LOGGER.warn("Ignoring chunk since it's not in the view range: {}, {}", chunkX, chunkZ);
        } else {
            int index = this.storage.getIndex(chunkX, chunkZ);
            LevelChunk chunk = this.storage.chunks.get(index);
            if (!isValidChunk(chunk, chunkX, chunkZ)) {
                LOGGER.warn("Ignoring chunk since it's not present: {}, {}", chunkX, chunkZ);
            } else {
                chunk.replaceBiomes(readBuffer);
            }
        }
    }
 
    public @Nullable LevelChunk replaceWithPacketData(
        int chunkX,
        int chunkZ,
        FriendlyByteBuf readBuffer,
        Map<Heightmap.Types, long[]> heightmaps,
        Consumer<ClientboundLevelChunkPacketData.BlockEntityTagOutput> blockEntities
    ) {
        if (!this.storage.inRange(chunkX, chunkZ)) {
            LOGGER.warn("Ignoring chunk since it's not in the view range: {}, {}", chunkX, chunkZ);
            return null;
        } else {
            int index = this.storage.getIndex(chunkX, chunkZ);
            LevelChunk chunk = this.storage.chunks.get(index);
            ChunkPos pos = new ChunkPos(chunkX, chunkZ);
            if (!isValidChunk(chunk, chunkX, chunkZ)) {
                chunk = new LevelChunk(this.level, pos);
                chunk.replaceWithPacketData(readBuffer, heightmaps, blockEntities);
                this.storage.replace(index, chunk);
            } else {
                chunk.replaceWithPacketData(readBuffer, heightmaps, blockEntities);
                this.storage.refreshEmptySections(chunk);
            }
 
            this.level.onChunkLoaded(pos);
            return chunk;
        }
    }
 
    @Override
    public void tick(BooleanSupplier haveTime, boolean tickChunks) {
    }
 
    public void updateViewCenter(int x, int z) {
        this.storage.viewCenterX = x;
        this.storage.viewCenterZ = z;
    }
 
    public void updateViewRadius(int viewRange) {
        int chunkRadius = this.storage.chunkRadius;
        int newChunkRadius = calculateStorageRange(viewRange);
        if (chunkRadius != newChunkRadius) {
            ClientChunkCache.Storage newStorage = new ClientChunkCache.Storage(newChunkRadius);
            newStorage.viewCenterX = this.storage.viewCenterX;
            newStorage.viewCenterZ = this.storage.viewCenterZ;
 
            for (int i = 0; i < this.storage.chunks.length(); i++) {
                LevelChunk chunk = this.storage.chunks.get(i);
                if (chunk != null) {
                    ChunkPos pos = chunk.getPos();
                    if (newStorage.inRange(pos.x(), pos.z())) {
                        newStorage.replace(newStorage.getIndex(pos.x(), pos.z()), chunk);
                    }
                }
            }
 
            this.storage = newStorage;
        }
    }
 
    private static int calculateStorageRange(int viewRange) {
        return Math.max(2, viewRange) + 3;
    }
 
    @Override
    public String gatherStats() {
        return this.storage.chunks.length() + ", " + this.getLoadedChunksCount();
    }
 
    @Override
    public int getLoadedChunksCount() {
        return this.storage.chunkCount;
    }
 
    @Override
    public void onLightUpdate(LightLayer layer, SectionPos pos) {
        Minecraft.getInstance().levelRenderer.setSectionDirty(pos.x(), pos.y(), pos.z());
    }
 
    public LongOpenHashSet getLoadedEmptySections() {
        return this.storage.loadedEmptySections;
    }
 
    @Override
    public void onSectionEmptinessChanged(int sectionX, int sectionY, int sectionZ, boolean empty) {
        this.storage.onSectionEmptinessChanged(sectionX, sectionY, sectionZ, empty);
    }
 
    @OnlyIn(Dist.CLIENT)
    private final class Storage {
        private final AtomicReferenceArray<@Nullable LevelChunk> chunks;
        private final LongOpenHashSet loadedEmptySections;
        private final int chunkRadius;
        private final int viewRange;
        private volatile int viewCenterX;
        private volatile int viewCenterZ;
        private int chunkCount;
 
        private Storage(int chunkRadius) {
            Objects.requireNonNull(ClientChunkCache.this);
            super();
            this.loadedEmptySections = new LongOpenHashSet();
            this.chunkRadius = chunkRadius;
            this.viewRange = chunkRadius * 2 + 1;
            this.chunks = new AtomicReferenceArray<>(this.viewRange * this.viewRange);
        }
 
        private int getIndex(int chunkX, int chunkZ) {
            return Math.floorMod(chunkZ, this.viewRange) * this.viewRange + Math.floorMod(chunkX, this.viewRange);
        }
 
        private void replace(int index, @Nullable LevelChunk newChunk) {
            LevelChunk removedChunk = this.chunks.getAndSet(index, newChunk);
            if (removedChunk != null) {
                this.chunkCount--;
                this.dropEmptySections(removedChunk);
                ClientChunkCache.this.level.unload(removedChunk);
            }
 
            if (newChunk != null) {
                this.chunkCount++;
                this.addEmptySections(newChunk);
            }
        }
 
        private void drop(int index, LevelChunk oldChunk) {
            if (this.chunks.compareAndSet(index, oldChunk, null)) {
                this.chunkCount--;
                this.dropEmptySections(oldChunk);
            }
 
            ClientChunkCache.this.level.unload(oldChunk);
        }
 
        public void onSectionEmptinessChanged(int sectionX, int sectionY, int sectionZ, boolean empty) {
            if (this.inRange(sectionX, sectionZ)) {
                long sectionNode = SectionPos.asLong(sectionX, sectionY, sectionZ);
                if (empty) {
                    this.loadedEmptySections.add(sectionNode);
                } else if (this.loadedEmptySections.remove(sectionNode)) {
                    ClientChunkCache.this.level.onSectionBecomingNonEmpty(sectionNode);
                }
            }
        }
 
        private void dropEmptySections(LevelChunk chunk) {
            LevelChunkSection[] sections = chunk.getSections();
 
            for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {
                ChunkPos chunkPos = chunk.getPos();
                this.loadedEmptySections.remove(SectionPos.asLong(chunkPos.x(), chunk.getSectionYFromSectionIndex(sectionIndex), chunkPos.z()));
            }
        }
 
        private void addEmptySections(LevelChunk chunk) {
            LevelChunkSection[] sections = chunk.getSections();
 
            for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {
                LevelChunkSection section = sections[sectionIndex];
                if (section.hasOnlyAir()) {
                    ChunkPos chunkPos = chunk.getPos();
                    this.loadedEmptySections.add(SectionPos.asLong(chunkPos.x(), chunk.getSectionYFromSectionIndex(sectionIndex), chunkPos.z()));
                }
            }
        }
 
        private void refreshEmptySections(LevelChunk chunk) {
            ChunkPos chunkPos = chunk.getPos();
            LevelChunkSection[] sections = chunk.getSections();
 
            for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {
                LevelChunkSection section = sections[sectionIndex];
                long sectionNode = SectionPos.asLong(chunkPos.x(), chunk.getSectionYFromSectionIndex(sectionIndex), chunkPos.z());
                if (section.hasOnlyAir()) {
                    this.loadedEmptySections.add(sectionNode);
                } else if (this.loadedEmptySections.remove(sectionNode)) {
                    ClientChunkCache.this.level.onSectionBecomingNonEmpty(sectionNode);
                }
            }
        }
 
        private boolean inRange(int chunkX, int chunkZ) {
            return Math.abs(chunkX - this.viewCenterX) <= this.chunkRadius && Math.abs(chunkZ - this.viewCenterZ) <= this.chunkRadius;
        }
 
        protected @Nullable LevelChunk getChunk(int index) {
            return this.chunks.get(index);
        }
 
        private void dumpChunks(String file) {
            try (FileOutputStream stream = new FileOutputStream(file)) {
                int chunkRadius = ClientChunkCache.this.storage.chunkRadius;
 
                for (int z = this.viewCenterZ - chunkRadius; z <= this.viewCenterZ + chunkRadius; z++) {
                    for (int x = this.viewCenterX - chunkRadius; x <= this.viewCenterX + chunkRadius; x++) {
                        LevelChunk chunk = ClientChunkCache.this.storage.chunks.get(ClientChunkCache.this.storage.getIndex(x, z));
                        if (chunk != null) {
                            ChunkPos pos = chunk.getPos();
                            stream.write((pos.x() + "\t" + pos.z() + "\t" + chunk.isEmpty() + "\n").getBytes(StandardCharsets.UTF_8));
                        }
                    }
                }
            } catch (IOException var10) {
                ClientChunkCache.LOGGER.error("Failed to dump chunks to file {}", file, var10);
            }
        }
    }
}

引用的其他类