Improve efficiency of entity chunk update

This commit is contained in:
TheMode 2021-04-23 10:17:42 +02:00
parent 9a5af9514e
commit c24cc07b7c
2 changed files with 66 additions and 61 deletions

View File

@ -43,6 +43,7 @@ import net.minestom.server.utils.time.Cooldown;
import net.minestom.server.utils.time.TimeUnit;
import net.minestom.server.utils.time.UpdateOption;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -469,7 +470,7 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer,
// Fix current chunk being null if the entity has been spawned before
if (currentChunk == null) {
currentChunk = instance.getChunkAt(position);
refreshCurrentChunk(instance.getChunkAt(position));
}
// Check if the entity chunk is loaded
@ -836,6 +837,12 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer,
return currentChunk;
}
@ApiStatus.Internal
protected void refreshCurrentChunk(Chunk currentChunk) {
this.currentChunk = currentChunk;
MinecraftServer.getUpdateManager().getThreadProvider().updateEntity(this);
}
/**
* Gets the entity current instance.
*
@ -869,7 +876,7 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer,
this.isActive = true;
this.instance = instance;
this.currentChunk = instance.getChunkAt(position.getX(), position.getZ());
refreshCurrentChunk(instance.getChunkAt(position.getX(), position.getZ()));
instance.UNSAFE_addEntity(this);
spawn();
EntitySpawnEvent entitySpawnEvent = new EntitySpawnEvent(this, instance);
@ -1343,7 +1350,7 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer,
player.refreshVisibleEntities(newChunk);
}
this.currentChunk = newChunk;
refreshCurrentChunk(newChunk);
}
}

View File

@ -5,8 +5,6 @@ import net.minestom.server.entity.Entity;
import net.minestom.server.entity.acquirable.AcquirableEntity;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.instance.SharedInstance;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.chunk.ChunkUtils;
import org.jetbrains.annotations.NotNull;
@ -30,6 +28,7 @@ public abstract class ThreadProvider {
private final Map<Chunk, ChunkEntry> chunkEntryMap = new HashMap<>();
// Iterated over to refresh the thread used to update entities & chunks
private final ArrayDeque<Chunk> chunks = new ArrayDeque<>();
private final Set<Entity> updatableEntities = ConcurrentHashMap.newKeySet();
private final Set<Entity> removedEntities = ConcurrentHashMap.newKeySet();
// Represents the maximum percentage of tick time
@ -84,15 +83,6 @@ public abstract class ThreadProvider {
return RefreshType.CONSTANT;
}
/**
* Defines how often entities thread should be updated.
*
* @return the refresh type
*/
public @NotNull RefreshType getEntityRefreshType() {
return RefreshType.CONSTANT;
}
/**
* Minimum time used to refresh chunks & entities thread.
*
@ -156,12 +146,18 @@ public abstract class ThreadProvider {
this.chunks.remove(chunk);
}
protected int getThreadId(Chunk chunk) {
/**
* Finds the thread id associated to a {@link Chunk}.
*
* @param chunk the chunk to find the thread id from
* @return the chunk thread id
*/
protected int getThreadId(@NotNull Chunk chunk) {
return (int) (Math.abs(findThread(chunk)) % threads.size());
}
/**
* Prepares the update.
* Prepares the update by creating the {@link TickThread} tasks.
*
* @param time the tick time in milliseconds
*/
@ -206,23 +202,25 @@ public abstract class ThreadProvider {
return countDownLatch;
}
/**
* Called at the end of each tick to clear removed entities,
* refresh the chunk linked to an entity, and chunk threads based on {@link #findThread(Chunk)}.
*
* @param tickTime the duration of the tick in ms,
* used to ensure that the refresh does not take more time than the tick itself
*/
public synchronized void refreshThreads(long tickTime) {
// Clear removed entities
{
for (Entity entity : removedEntities) {
AcquirableEntity acquirableEntity = entity.getAcquirable();
ChunkEntry chunkEntry = acquirableEntity.getHandler().getChunkEntry();
// Remove from list
if (chunkEntry != null) {
chunkEntry.entities.remove(entity);
}
}
this.removedEntities.clear();
processRemovedEntities();
}
final boolean chunkRefresh = getChunkRefreshType() != RefreshType.NEVER;
final boolean entityRefresh = getEntityRefreshType() != RefreshType.NEVER;
if (!chunkRefresh && !entityRefresh)
// Update entities chunks
{
processUpdatedEntities();
}
if (getChunkRefreshType() == RefreshType.NEVER)
return;
final int timeOffset = MathUtils.clamp((int) ((double) tickTime * refreshPercentage),
@ -238,20 +236,7 @@ public abstract class ThreadProvider {
}
// Update chunk threads
if (chunkRefresh) {
switchChunk(chunk);
}
// Update entities
if (entityRefresh) {
Instance instance = chunk.getInstance();
refreshEntitiesThread(instance, chunk);
if (instance instanceof InstanceContainer) {
for (SharedInstance sharedInstance : ((InstanceContainer) instance).getSharedInstances()) {
refreshEntitiesThread(sharedInstance, chunk);
}
}
}
switchChunk(chunk);
// Add back to the deque
chunks.addLast(chunk);
@ -261,10 +246,13 @@ public abstract class ThreadProvider {
if (System.currentTimeMillis() >= endTime)
break;
}
}
public void updateEntity(@NotNull Entity entity) {
this.updatableEntities.add(entity);
}
public void removeEntity(@NotNull Entity entity) {
this.removedEntities.add(entity);
}
@ -277,34 +265,44 @@ public abstract class ThreadProvider {
return threads;
}
private void refreshEntitiesThread(Instance instance, Chunk chunk) {
var entities = instance.getChunkEntities(chunk);
for (Entity entity : entities) {
private void processRemovedEntities() {
for (Entity entity : removedEntities) {
AcquirableEntity acquirableEntity = entity.getAcquirable();
ChunkEntry chunkEntry = acquirableEntity.getHandler().getChunkEntry();
// Remove from list
if (chunkEntry != null) {
chunkEntry.entities.remove(entity);
}
}
this.removedEntities.clear();
}
private void processUpdatedEntities() {
for (Entity entity : updatableEntities) {
AcquirableEntity acquirableEntity = entity.getAcquirable();
ChunkEntry handlerChunkEntry = acquirableEntity.getHandler().getChunkEntry();
Chunk batchChunk = handlerChunkEntry != null ? handlerChunkEntry.getChunk() : null;
Chunk entityChunk = entity.getChunk();
if (!Objects.equals(batchChunk, entityChunk)) {
// Entity is possibly not in the correct thread
// Remove from previous list
{
if (handlerChunkEntry != null) {
handlerChunkEntry.entities.remove(entity);
}
// Entity is possibly not in the correct thread
// Remove from previous list
{
if (handlerChunkEntry != null) {
handlerChunkEntry.entities.remove(entity);
}
}
// Add to new list
{
ChunkEntry chunkEntry = chunkEntryMap.get(entityChunk);
if (chunkEntry != null) {
chunkEntry.entities.add(entity);
acquirableEntity.getHandler().refreshChunkEntry(chunkEntry);
}
// Add to new list
{
ChunkEntry chunkEntry = chunkEntryMap.get(entityChunk);
if (chunkEntry != null) {
chunkEntry.entities.add(entity);
acquirableEntity.getHandler().refreshChunkEntry(chunkEntry);
}
}
}
this.updatableEntities.clear();
}
/**