/* * This file is part of LanternServer, licensed under the MIT License (MIT). * * Copyright (c) LanternPowered <https://www.lanternpowered.org> * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * 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.lanternpowered.server.world.chunk; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3i; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableSet; import org.lanternpowered.server.game.Lantern; import org.spongepowered.api.data.DataContainer; import org.spongepowered.api.util.GuavaCollectors; import java.util.Arrays; import java.util.LinkedList; import javax.annotation.Nullable; class LanternLoadingTicket implements ChunkLoadingTicket { final LinkedList<Vector2i> queue = new LinkedList<>(); private final LanternChunkManager chunkManager; private final String plugin; // The extra data, can be attached by mods, just keep it safe until // sponge decides to add a api for it @Nullable DataContainer extraData; // The maximum amount of chunks that can be loaded by this ticket private final int maxChunks; // The amount of chunks that may be loaded by this ticket private int numChunks; // Whether the ticket is released and may not be used again private boolean released; LanternLoadingTicket(String plugin, LanternChunkManager chunkManager, int maxChunks) { this(plugin, chunkManager, maxChunks, maxChunks); } LanternLoadingTicket(String plugin, LanternChunkManager chunkManager, int maxChunks, int numChunks) { this.numChunks = Math.min(numChunks, maxChunks); this.chunkManager = chunkManager; this.maxChunks = maxChunks; this.plugin = plugin; } /** * Gets whether there no entries are inside the ticket. * * @return Is empty */ public boolean isEmpty() { synchronized (this.queue) { return this.queue.isEmpty(); } } @Override public boolean setNumChunks(int numChunks) { checkArgument(numChunks >= 0, "numChunks may not be negative"); synchronized (this.queue) { if (numChunks > this.maxChunks) { return false; } // Remove the oldest chunks that cannot be loaded anymore if (!this.released && numChunks < this.numChunks) { int size = this.queue.size(); if (numChunks < size) { for (int i = 0; i < size - numChunks; i++) { this.chunkManager.unforce(this, this.queue.poll(), true); } } } this.numChunks = numChunks; } return true; } @Override public int getNumChunks() { synchronized (this.queue) { return this.numChunks; } } @Override public int getMaxNumChunks() { return this.maxChunks; } @Override public String getPlugin() { return this.plugin; } @Override public ImmutableSet<Vector3i> getChunkList() { synchronized (this.queue) { if (this.released) { return ImmutableSet.of(); } return this.queue.stream().map(v -> new Vector3i(v.getX(), 0, v.getY())) .collect(GuavaCollectors.toImmutableSet()); } } @Override public void forceChunk(Vector3i chunk) { this.forceChunk(checkNotNull(chunk, "chunk").toVector2(true)); } @Override public boolean forceChunk(Vector2i chunk) { checkNotNull(chunk, "chunk"); synchronized (this.queue) { if (this.released) { Lantern.getLogger().warn("The plugin {} attempted to force load a chunk with an invalid ticket. " + "This is not permitted.", this.plugin); return false; } // Only force if not done before if (!this.queue.contains(chunk)) { // Remove the oldest chunk if necessary if (this.queue.size() >= this.numChunks) { this.chunkManager.unforce(this, this.queue.poll(), true); } this.queue.add(chunk); this.chunkManager.force(this, chunk); return true; } } return false; } @Override public void unforceChunk(Vector3i chunk) { this.unforceChunk(checkNotNull(chunk, "chunk").toVector2(true)); } @Override public boolean unforceChunk(Vector2i chunk) { final Vector2i chunk0 = checkNotNull(chunk, "chunk"); synchronized (this.queue) { if (this.released) { return false; } if (this.queue.remove(chunk0)) { this.chunkManager.unforce(this, chunk0, true); return true; } } return false; } @Override public void unforceChunks() { synchronized (this.queue) { if (this.released) { return; } while (!this.queue.isEmpty()) { this.chunkManager.unforce(this, this.queue.poll(), true); } } } @Override public boolean isReleased() { synchronized (this.queue) { return this.released; } } @Override public void prioritizeChunk(Vector3i chunk) { checkNotNull(chunk, "chunk"); synchronized (this.queue) { if (this.released) { return; } final Vector2i chunk0 = chunk.toVector2(true); // Move the chunk to the bottom of the queue if found if (this.queue.remove(chunk0)) { this.queue.add(chunk0); } } } @Override public void release() { synchronized (this.queue) { this.unforceChunks(); this.chunkManager.release(this); this.released = true; } } MoreObjects.ToStringHelper toStringHelper() { synchronized (this.queue) { return MoreObjects.toStringHelper(this) .add("plugin", this.plugin) .add("maxChunks", this.maxChunks) .add("numChunks", this.numChunks) .add("released", this.released) .add("chunks", Arrays.toString(this.queue.toArray(new Vector2i[this.queue.size()]))); } } @Override public String toString() { return this.toStringHelper().toString(); } }