/*
* Copyright 2013 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.rendering.world;
import com.google.common.collect.Lists;
import com.google.common.collect.Queues;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.math.ChunkMath;
import org.terasology.math.geom.Vector3f;
import org.terasology.math.geom.Vector3i;
import org.terasology.monitoring.chunk.ChunkMonitor;
import org.terasology.rendering.primitives.ChunkMesh;
import org.terasology.rendering.primitives.ChunkTessellator;
import org.terasology.utilities.concurrency.TaskMaster;
import org.terasology.world.ChunkView;
import org.terasology.world.WorldProvider;
import org.terasology.world.chunks.ChunkConstants;
import org.terasology.world.chunks.RenderableChunk;
import org.terasology.world.chunks.pipeline.ChunkTask;
import org.terasology.world.chunks.pipeline.ShutdownChunkTask;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.ConcurrentHashMap;
/**
* Provides the mechanism for updating and generating chunk meshes.
*
*/
public final class ChunkMeshUpdateManager {
private static final int NUM_TASK_THREADS = 8;
private static final Logger logger = LoggerFactory.getLogger(ChunkMeshUpdateManager.class);
/* CHUNK UPDATES */
private final Set<RenderableChunk> chunksProcessing = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final BlockingDeque<RenderableChunk> chunksComplete = Queues.newLinkedBlockingDeque();
private TaskMaster<ChunkTask> chunkUpdater;
private final ChunkTessellator tessellator;
private final WorldProvider worldProvider;
/**
* This variable is volatile, so that it's value is visible to worker thread that calculates the best task to
* process
*/
private volatile float cameraChunkPosX;
private volatile float cameraChunkPosY;
private volatile float cameraChunkPosZ;
public ChunkMeshUpdateManager(ChunkTessellator tessellator, WorldProvider worldProvider) {
this.tessellator = tessellator;
this.worldProvider = worldProvider;
chunkUpdater = TaskMaster.createDynamicPriorityTaskMaster("Chunk-Updater", NUM_TASK_THREADS, new ChunkUpdaterComparator());
}
/**
* Updates the given chunk using a new thread from the thread pool. If the maximum amount of chunk updates
* is reached, the chunk update is ignored. Chunk updates can be forced though.
*
* @param chunk The chunk to update
* @return True if a chunk update was executed
*/
// TODO: Review this system
public boolean queueChunkUpdate(RenderableChunk chunk) {
if (!chunksProcessing.contains(chunk)) {
executeChunkUpdate(chunk);
return true;
}
return false;
}
/**
* The method tells the chunk mesh update manager where the camera is, so that is able to prioritize chunks near the
* camera. It stores the values in volatile variables so that the change is visible to the chunk updating threads
* immediately.
*/
public void setCameraPosition(Vector3f cameraPosition) {
Vector3i chunkPos = ChunkMath.calcChunkPos(cameraPosition);
cameraChunkPosX = chunkPos.x;
cameraChunkPosY = chunkPos.y;
cameraChunkPosZ = chunkPos.z;
}
public List<RenderableChunk> availableChunksForUpdate() {
List<RenderableChunk> result = Lists.newArrayListWithExpectedSize(chunksComplete.size());
chunksComplete.drainTo(result);
chunksProcessing.removeAll(result);
return result;
}
private void executeChunkUpdate(final RenderableChunk c) {
chunksProcessing.add(c);
ChunkUpdateTask task = new ChunkUpdateTask(c, tessellator, worldProvider, this);
try {
chunkUpdater.put(task);
} catch (InterruptedException e) {
logger.error("Failed to enqueue task {}", task, e);
}
}
private void finishedProcessing(RenderableChunk c) {
chunksComplete.add(c);
}
public void shutdown() {
chunkUpdater.shutdown(new ShutdownChunkTask(), false);
}
private static class ChunkUpdateTask implements ChunkTask {
private RenderableChunk c;
private ChunkTessellator tessellator;
private WorldProvider worldProvider;
private ChunkMeshUpdateManager chunkMeshUpdateManager;
ChunkUpdateTask(RenderableChunk chunk, ChunkTessellator tessellator, WorldProvider worldProvider, ChunkMeshUpdateManager chunkMeshUpdateManager) {
this.chunkMeshUpdateManager = chunkMeshUpdateManager;
this.c = chunk;
this.tessellator = tessellator;
this.worldProvider = worldProvider;
}
@Override
public Vector3i getPosition() {
return c.getPosition();
}
@Override
public String getName() {
return "Update chunk";
}
@Override
public boolean isTerminateSignal() {
return false;
}
@Override
public void run() {
ChunkMesh newMesh;
ChunkView chunkView = worldProvider.getLocalView(c.getPosition());
if (chunkView != null) {
/*
* Important set dirty flag first, so that a concurrent modification of the chunk in the mean time we
* will end up with a dirty chunk.
*/
c.setDirty(false);
if (chunkView.isValidView()) {
newMesh = tessellator.generateMesh(chunkView, ChunkConstants.SIZE_Y, 0);
c.setPendingMesh(newMesh);
ChunkMonitor.fireChunkTessellated(c.getPosition(), newMesh);
}
}
chunkMeshUpdateManager.finishedProcessing(c);
// Clean these up because the task executor holds the object in memory.
c = null;
tessellator = null;
worldProvider = null;
}
}
private class ChunkUpdaterComparator implements Comparator<ChunkTask> {
@Override
public int compare(ChunkTask o1, ChunkTask o2) {
return score(o1) - score(o2);
}
private int score(ChunkTask task) {
if (task.isTerminateSignal()) {
return -1;
}
return distFromRegion(task.getPosition(), new Vector3i(cameraChunkPosX, cameraChunkPosY, cameraChunkPosZ));
}
private int distFromRegion(Vector3i pos, Vector3i regionCenter) {
return pos.gridDistance(regionCenter);
}
}
}