org.diorite.impl.world.chunk.ChunkManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.diorite.impl.world.chunk.ChunkManagerImpl.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016. Diorite (by Bartomiej Mazur (aka GotoFinal))
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package org.diorite.impl.world.chunk;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import org.diorite.impl.DioriteCore;
import org.diorite.impl.Tickable;
import org.diorite.impl.world.WorldImpl;
import org.diorite.impl.world.generator.ChunkBuilderImpl;
import org.diorite.impl.world.io.ChunkIOService;
import org.diorite.impl.world.io.requests.ChunkLoadRequest;
import org.diorite.event.EventType;
import org.diorite.event.chunk.ChunkGenerateEvent;
import org.diorite.event.chunk.ChunkLoadEvent;
import org.diorite.event.chunk.ChunkPopulateEvent;
import org.diorite.utils.math.endian.BigEndianUtils;
import org.diorite.world.chunk.Chunk;
import org.diorite.world.chunk.ChunkManager;
import org.diorite.world.chunk.ChunkPos;
import org.diorite.world.generator.WorldGenerator;
import org.diorite.world.generator.maplayer.MapLayer;

import it.unimi.dsi.fastutil.longs.LongCollection;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;

public class ChunkManagerImpl implements ChunkManager, Tickable {

    /**
     * Core instance.
     */
    private final DioriteCore core;

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

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

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

    /**
     * The biome maps used to fill chunks biome grid and terrain generation.
     */
    private final MapLayer[] biomeGrid;

    /**
     * A map of chunks currently loaded in memory.
     */
    private final ConcurrentMap<Long, ChunkImpl> chunks = new ConcurrentHashMap<>(1000, .25f, 8);

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

    public ChunkManagerImpl(final DioriteCore core, final WorldImpl world, final ChunkIOService service,
            final WorldGenerator generator) {
        this.core = core;
        this.world = world;
        this.service = service;
        this.generator = generator;
        this.biomeGrid = MapLayer.initialize(world.getSeed(), world.getDimension(), world.getWorldType());
    }

    public WorldGenerator getGenerator() {
        return this.generator;
    }

    public ChunkIOService getService() {
        return this.service;
    }

    public MapLayer[] getBiomeGrid() {
        return this.biomeGrid;
    }

    public WorldImpl getWorld() {
        return this.world;
    }

    @Override
    public ChunkImpl getChunk(final ChunkPos pos) {
        return this.getChunk(pos.getX(), pos.getZ());
    }

    @Override
    public ChunkImpl getChunk(final int x, final int z) {
        final Long key = BigEndianUtils.toLong(x, z);
        if (this.chunks.containsKey(key)) {
            return this.chunks.get(key);
        } else {
            // only create chunk if it's not in the map already
            final ChunkImpl chunk = new ChunkImpl(new ChunkPos(x, z, this.world));
            final ChunkImpl prev = this.chunks.putIfAbsent(key, chunk);
            // if it was created in the intervening time, the earlier one wins
            return (prev == null) ? chunk : prev;
        }
    }

    @Override
    public boolean isChunkLoaded(final int x, final int z) {
        final Long key = BigEndianUtils.toLong(x, z);
        final ChunkImpl chunk = this.chunks.get(key);
        return (chunk != null) && chunk.isLoaded();
    }

    @Override
    public boolean isChunkInUse(final int x, final int z) {
        final Long key = BigEndianUtils.toLong(x, z);
        final Set<ChunkLock> lockSet = this.locks.get(key);
        return (lockSet != null) && !lockSet.isEmpty();
    }

    public void loadChunkAsync(final int x, final int z, final boolean generate,
            final BiConsumer<ChunkImpl, Boolean> onEnd) {
        final ChunkImpl chunk = this.getChunk(x, z);
        if (chunk.isLoaded()) {
            onEnd.accept(chunk, false);
            return;
        }
        final ChunkLoadRequest chunkLoadRequest = new ChunkLoadRequest(ChunkIOService.INSTANT_PRIORITY, chunk, x,
                z);
        chunkLoadRequest.addOnEnd(r -> {
            ChunkImpl loadedChunk = r.get();
            if (generate && (loadedChunk == null)) {
                this.core.sync(() -> {
                    final ChunkGenerateEvent genEvt = new ChunkGenerateEvent(chunk);
                    EventType.callEvent(genEvt);
                });
                loadedChunk = chunk;
            } else if (loadedChunk == null) {
                loadedChunk = chunk;
            }
            onEnd.accept(loadedChunk, true);
        });
        this.service.queue(chunkLoadRequest);
    }

    @Override
    public boolean loadChunk(final int x, final int z, final boolean generate) {
        final ChunkLoadEvent loadEvt = new ChunkLoadEvent(new ChunkPos(x, z, this.world));
        EventType.callEvent(loadEvt);
        if (loadEvt.isCancelled()) {
            return false;
        }
        final ChunkImpl chunk = (ChunkImpl) loadEvt.getLoadedChunk();

        if (chunk == null) {
            throw new NullPointerException("Loaded null chunk from: " + x + ", " + z);
        }
        // stop here if we can't generate
        if (!generate || !loadEvt.isNeedBeGenerated()) {
            return false;
        }

        // get generating
        final ChunkGenerateEvent genEvt = new ChunkGenerateEvent(chunk);
        EventType.callEvent(genEvt);
        return !genEvt.isCancelled();
    }

    @Override
    public void unloadOldChunks() {
        for (final Map.Entry<Long, ChunkImpl> entry : this.chunks.entrySet()) {
            final Set<ChunkLock> lockSet = this.locks.get(entry.getKey());
            if ((lockSet == null) || lockSet.isEmpty()) {
                if (!entry.getValue().unload(true, true)) {
                    System.err.println(
                            "[ChunkIO] Failed to unload chunk " + this.world.getName() + ":" + entry.getKey());
                }
            }
            // cannot remove old chunks from cache - Block and BlockState keep references.
            // they must either be changed to look up the chunk again all the time, or this code left out.
            /*if (!entry.getValue().isLoaded()) {
            chunks.entrySet().remove(entry);
            locks.remove(entry.getKey());
            }*/
        }
    }

    @Override
    public void populateChunk(final int x, final int z, final boolean force) {
        final ChunkImpl chunk = this.getChunk(x, z);
        final ChunkPopulateEvent popEvt = new ChunkPopulateEvent(chunk, force);
        EventType.callEvent(popEvt);
    }

    @Override
    public void forcePopulation(final int x, final int z) {
        try {
            final ChunkImpl chunk = this.getChunk(x, z);
            final ChunkPopulateEvent popEvt = new ChunkPopulateEvent(chunk, true);
            EventType.callEvent(popEvt);
        } catch (final Throwable e) {
            System.err.println("[ChunkIO] Error while populating chunk (" + x + "," + z + ")");
            e.printStackTrace();
        }
    }

    @Override
    public void generateChunk(final Chunk chunk, final int x, final int z) {
        final ChunkPos pos = new ChunkPos(x, z, this.world);
        this.generator.generate(this.generator.generateBiomes(new ChunkBuilderImpl(this.biomeGrid), pos), pos)
                .init(chunk);
    }

    @Override
    public boolean forceRegeneration(final int x, final int z) {
        final ChunkImpl chunk = this.getChunk(x, z);

        if ((chunk == null) || !chunk.unload(false, false)) {
            return false;
        }
        chunk.setPopulated(false);
        try {
            final ChunkGenerateEvent genEvt = new ChunkGenerateEvent(chunk);
            EventType.callEvent(genEvt);
            if (genEvt.isCancelled()) {
                return false;
            }
            final ChunkPopulateEvent popEvt = new ChunkPopulateEvent(chunk, false); // should this be forced?
            EventType.callEvent(popEvt);
            if (popEvt.isCancelled()) {
                return false;
            }
        } catch (final Throwable e) {
            System.err.println("[ChunkIO] Error while regenerating chunk (" + x + "," + z + ")");
            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public List<ChunkImpl> getLoadedChunks() {
        return this.chunks.values().stream().filter(ChunkImpl::isLoaded).collect(Collectors.toList());
    }

    @Override
    public boolean save(final Chunk chunk) {
        if (chunk.isLoaded()) {
            this.service.queueChunkSave((ChunkImpl) chunk, ChunkIOService.MEDIUM_PRIORITY);
            return true;
        }
        return false;
    }

    @Override
    public boolean save(final Chunk chunk, final int priority) {
        if (chunk.isLoaded()) {
            this.service.queueChunkSave((ChunkImpl) chunk, priority);
            return true;
        }
        return false;
    }

    public int[] getBiomeGridAtLowerRes(final int x, final int z, final int sizeX, final int sizeZ) {
        return this.biomeGrid[1].generateValues(x, z, sizeX, sizeZ);
    }

    @Override
    public void doTick(final int tps) {
        this.chunks.values().stream().filter(ChunkImpl::isLoaded)
                .forEach(c -> c.getTileEntities().forEach((l, t) -> t.doTick(tps)));
    }

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

    /**
     * A group of locks on chunks to prevent them from being unloaded while in use.
     */
    public static class ChunkLock {
        private final ChunkManagerImpl cm;
        private final String desc;
        private final LongCollection keys = new LongOpenHashSet(3);

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

        public synchronized void acquire(final long key) {
            if (this.keys.contains(key)) {
                return;
            }
            this.keys.add(key);
            this.cm.getLockSet(key).add(this);
        }

        public synchronized void release(final long key) {
            if (!this.keys.contains(key)) {
                return;
            }
            this.keys.remove(key);
            this.cm.getLockSet(key).remove(this);
        }

        public synchronized void clear() {
            this.keys.forEach(key -> this.cm.getLockSet(key).remove(this));
            this.keys.clear();
        }

        //        public TLongIterator iterator()
        //        {
        //            return this.keys.iterator();
        //        }

        @Override
        public String toString() {
            return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).appendSuper(super.toString())
                    .append("cm", this.cm).append("desc", this.desc).append("keys", this.keys).toString();
        }
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).appendSuper(super.toString())
                .append("world", this.world).append("generator", this.generator).toString();
    }
}