mirror of
https://github.com/Minestom/Minestom.git
synced 2024-11-10 12:50:17 +01:00
Reduce the overhead of converting chunk indexes to chunk object, dont tick empty entity list
This commit is contained in:
parent
05192de8e1
commit
5ef4d0f9b4
@ -1,6 +1,7 @@
|
||||
package net.minestom.server;
|
||||
|
||||
import com.google.common.collect.Queues;
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.InstanceManager;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
@ -183,13 +184,12 @@ public final class UpdateManager {
|
||||
* WARNING: should be automatically done by the {@link Instance} implementation.
|
||||
*
|
||||
* @param instance the instance of the chunk
|
||||
* @param chunkX the chunk X
|
||||
* @param chunkZ the chunk Z
|
||||
* @param chunk the loaded chunk
|
||||
*/
|
||||
public synchronized void signalChunkLoad(Instance instance, int chunkX, int chunkZ) {
|
||||
public synchronized void signalChunkLoad(Instance instance, @NotNull Chunk chunk) {
|
||||
if (this.threadProvider == null)
|
||||
return;
|
||||
this.threadProvider.onChunkLoad(instance, chunkX, chunkZ);
|
||||
this.threadProvider.onChunkLoad(instance, chunk);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -198,13 +198,12 @@ public final class UpdateManager {
|
||||
* WARNING: should be automatically done by the {@link Instance} implementation.
|
||||
*
|
||||
* @param instance the instance of the chunk
|
||||
* @param chunkX the chunk X
|
||||
* @param chunkZ the chunk Z
|
||||
* @param chunk the unloaded chunk
|
||||
*/
|
||||
public synchronized void signalChunkUnload(Instance instance, int chunkX, int chunkZ) {
|
||||
public synchronized void signalChunkUnload(Instance instance, @NotNull Chunk chunk) {
|
||||
if (this.threadProvider == null)
|
||||
return;
|
||||
this.threadProvider.onChunkUnload(instance, chunkX, chunkZ);
|
||||
this.threadProvider.onChunkUnload(instance, chunk);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -485,8 +485,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
|
||||
* @return an unmodifiable {@link Set} containing all the entities in a chunk,
|
||||
* if {@code chunk} is unloaded, return an empty {@link HashSet}
|
||||
*/
|
||||
@NotNull
|
||||
public Set<Entity> getChunkEntities(Chunk chunk) {
|
||||
public @NotNull Set<Entity> getChunkEntities(Chunk chunk) {
|
||||
if (!ChunkUtils.isLoaded(chunk))
|
||||
return new HashSet<>();
|
||||
|
||||
|
@ -506,7 +506,7 @@ public class InstanceContainer extends Instance {
|
||||
protected void retrieveChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) {
|
||||
final boolean loaded = chunkLoader.loadChunk(this, chunkX, chunkZ, chunk -> {
|
||||
cacheChunk(chunk);
|
||||
UPDATE_MANAGER.signalChunkLoad(this, chunkX, chunkZ);
|
||||
UPDATE_MANAGER.signalChunkLoad(this, chunk);
|
||||
// Execute callback and event in the instance thread
|
||||
scheduleNextTick(instance -> {
|
||||
callChunkLoadEvent(chunkX, chunkZ);
|
||||
@ -544,7 +544,7 @@ public class InstanceContainer extends Instance {
|
||||
OptionalCallback.execute(callback, chunk);
|
||||
}
|
||||
|
||||
UPDATE_MANAGER.signalChunkLoad(this, chunkX, chunkZ);
|
||||
UPDATE_MANAGER.signalChunkLoad(this, chunk);
|
||||
callChunkLoadEvent(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
@ -641,7 +641,7 @@ public class InstanceContainer extends Instance {
|
||||
final Chunk copiedChunk = chunk.copy(chunkX, chunkZ);
|
||||
|
||||
copiedInstance.cacheChunk(copiedChunk);
|
||||
UPDATE_MANAGER.signalChunkLoad(copiedInstance, chunkX, chunkZ);
|
||||
UPDATE_MANAGER.signalChunkLoad(copiedInstance, copiedChunk);
|
||||
}
|
||||
|
||||
return copiedInstance;
|
||||
@ -682,7 +682,7 @@ public class InstanceContainer extends Instance {
|
||||
* Adds a {@link Chunk} to the internal instance map.
|
||||
* <p>
|
||||
* WARNING: the chunk will not automatically be sent to players and
|
||||
* {@link net.minestom.server.UpdateManager#signalChunkLoad(Instance, int, int)} must be called manually.
|
||||
* {@link net.minestom.server.UpdateManager#signalChunkLoad(Instance, Chunk)} must be called manually.
|
||||
*
|
||||
* @param chunk the chunk to cache
|
||||
*/
|
||||
@ -823,7 +823,7 @@ public class InstanceContainer extends Instance {
|
||||
|
||||
chunk.unload();
|
||||
|
||||
UPDATE_MANAGER.signalChunkUnload(this, chunkX, chunkZ);
|
||||
UPDATE_MANAGER.signalChunkUnload(this, chunk);
|
||||
}
|
||||
this.scheduledChunksToRemove.clear();
|
||||
}
|
||||
|
@ -1,173 +0,0 @@
|
||||
package net.minestom.server.thread;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongArraySet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* Separate chunks into group of linked chunks
|
||||
* <p>
|
||||
* (1 chunks group = 1 thread execution)
|
||||
*/
|
||||
public class PerGroupChunkProvider extends ThreadProvider {
|
||||
|
||||
/**
|
||||
* Chunk -> its chunk group
|
||||
*/
|
||||
private final Map<Instance, Long2ObjectMap<LongSet>> instanceChunksGroupMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Used to know to which instance is linked a Set of chunks
|
||||
*/
|
||||
private final Map<Instance, Map<LongSet, Instance>> instanceInstanceMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onInstanceCreate(@NotNull Instance instance) {
|
||||
this.instanceChunksGroupMap.putIfAbsent(instance, new Long2ObjectOpenHashMap<>());
|
||||
this.instanceInstanceMap.putIfAbsent(instance, new HashMap<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInstanceDelete(@NotNull Instance instance) {
|
||||
this.instanceChunksGroupMap.remove(instance);
|
||||
this.instanceInstanceMap.remove(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkLoad(@NotNull Instance instance, int chunkX, int chunkZ) {
|
||||
final long loadedChunkIndex = ChunkUtils.getChunkIndex(chunkX, chunkZ);
|
||||
|
||||
Long2ObjectMap<LongSet> chunksGroupMap = getChunksGroupMap(instance);
|
||||
Map<LongSet, Instance> instanceMap = getInstanceMap(instance);
|
||||
|
||||
// List of groups which are neighbours
|
||||
List<LongSet> neighboursGroups = new ArrayList<>();
|
||||
|
||||
final long[] chunks = ChunkUtils.getNeighbours(instance, chunkX, chunkZ);
|
||||
boolean findGroup = false;
|
||||
for (long chunkIndex : chunks) {
|
||||
if (chunksGroupMap.containsKey(chunkIndex)) {
|
||||
final LongSet group = chunksGroupMap.get(chunkIndex);
|
||||
neighboursGroups.add(group);
|
||||
chunksGroupMap.remove(chunkIndex);
|
||||
instanceMap.remove(group);
|
||||
findGroup = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!findGroup) {
|
||||
// Create group of one chunk
|
||||
LongSet chunkIndexes = new LongArraySet();
|
||||
chunkIndexes.add(loadedChunkIndex);
|
||||
|
||||
chunksGroupMap.put(loadedChunkIndex, chunkIndexes);
|
||||
instanceMap.put(chunkIndexes, instance);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// The size of the final list, used as the initial capacity
|
||||
final int size = neighboursGroups.stream().mapToInt(Set::size).sum() + 1;
|
||||
|
||||
// Represent the merged group of all the neighbours
|
||||
LongSet finalGroup = new LongArraySet(size);
|
||||
|
||||
// Add the newly loaded chunk to the group
|
||||
finalGroup.add(loadedChunkIndex);
|
||||
|
||||
// Add all the neighbours groups to the final one
|
||||
for (LongSet chunkCoordinates : neighboursGroups) {
|
||||
finalGroup.addAll(chunkCoordinates);
|
||||
}
|
||||
|
||||
// Complete maps
|
||||
for (long index : finalGroup) {
|
||||
chunksGroupMap.put(index, finalGroup);
|
||||
}
|
||||
|
||||
instanceMap.put(finalGroup, instance);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkUnload(@NotNull Instance instance, int chunkX, int chunkZ) {
|
||||
Long2ObjectMap<LongSet> chunksGroupMap = getChunksGroupMap(instance);
|
||||
Map<LongSet, Instance> instanceMap = getInstanceMap(instance);
|
||||
|
||||
final long chunkIndex = ChunkUtils.getChunkIndex(chunkX, chunkZ);
|
||||
if (chunksGroupMap.containsKey(chunkIndex)) {
|
||||
// The unloaded chunk is part of a group, remove it from the group
|
||||
LongSet chunkCoordinates = chunksGroupMap.get(chunkIndex);
|
||||
chunkCoordinates.remove(chunkIndex);
|
||||
chunksGroupMap.remove(chunkIndex);
|
||||
|
||||
if (chunkCoordinates.isEmpty()) {
|
||||
// The chunk group is empty, remove it entirely
|
||||
instanceMap.entrySet().removeIf(entry -> entry.getKey().isEmpty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<Future<?>> update(long time) {
|
||||
|
||||
List<Future<?>> futures;
|
||||
int potentialSize = 0;
|
||||
|
||||
// Compute the potential array size
|
||||
{
|
||||
for (Map<LongSet, Instance> longSetInstanceMap : instanceInstanceMap.values()) {
|
||||
potentialSize += 1 + longSetInstanceMap.size();
|
||||
}
|
||||
futures = new ArrayList<>(potentialSize);
|
||||
}
|
||||
|
||||
instanceInstanceMap.forEach((instance, instanceMap) -> {
|
||||
|
||||
// True if the instance ended its tick call
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
|
||||
// instance tick
|
||||
futures.add(pool.submit(() -> {
|
||||
updateInstance(instance, time);
|
||||
countDownLatch.countDown();
|
||||
}));
|
||||
|
||||
// Update all the chunks
|
||||
instanceMap.keySet().forEach(chunksIndexes -> futures.add(pool.submit(() -> {
|
||||
// Wait for the instance to be updated
|
||||
// Needed because the instance tick is used to unload waiting chunks
|
||||
try {
|
||||
countDownLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
|
||||
// Tick all this chunk group
|
||||
chunksIndexes.forEach((long chunkIndex) -> processChunkTick(instance, chunkIndex, time));
|
||||
})));
|
||||
});
|
||||
|
||||
return futures;
|
||||
}
|
||||
|
||||
private Long2ObjectMap<LongSet> getChunksGroupMap(Instance instance) {
|
||||
return instanceChunksGroupMap.computeIfAbsent(instance, inst -> new Long2ObjectOpenHashMap<>());
|
||||
}
|
||||
|
||||
private Map<LongSet, Instance> getInstanceMap(Instance instance) {
|
||||
return instanceInstanceMap.computeIfAbsent(instance, inst -> new HashMap<>());
|
||||
}
|
||||
|
||||
}
|
@ -1,15 +1,14 @@
|
||||
package net.minestom.server.thread;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongArraySet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
@ -17,11 +16,11 @@ import java.util.concurrent.Future;
|
||||
*/
|
||||
public class PerInstanceThreadProvider extends ThreadProvider {
|
||||
|
||||
private final Map<Instance, LongSet> instanceChunkMap = new HashMap<>();
|
||||
private final Map<Instance, Set<Chunk>> instanceChunkMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onInstanceCreate(@NotNull Instance instance) {
|
||||
this.instanceChunkMap.putIfAbsent(instance, new LongArraySet());
|
||||
this.instanceChunkMap.putIfAbsent(instance, ConcurrentHashMap.newKeySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -30,20 +29,16 @@ public class PerInstanceThreadProvider extends ThreadProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkLoad(@NotNull Instance instance, int chunkX, int chunkZ) {
|
||||
public void onChunkLoad(@NotNull Instance instance, @NotNull Chunk chunk) {
|
||||
// Add the loaded chunk to the instance chunks list
|
||||
LongSet chunkCoordinates = getChunkCoordinates(instance);
|
||||
final long index = ChunkUtils.getChunkIndex(chunkX, chunkZ);
|
||||
chunkCoordinates.add(index);
|
||||
Set<Chunk> chunks = getChunks(instance);
|
||||
chunks.add(chunk);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkUnload(@NotNull Instance instance, int chunkX, int chunkZ) {
|
||||
LongSet chunkCoordinates = getChunkCoordinates(instance);
|
||||
final long index = ChunkUtils.getChunkIndex(chunkX, chunkZ);
|
||||
// Remove the unloaded chunk from the instance list
|
||||
chunkCoordinates.remove(index);
|
||||
|
||||
public void onChunkUnload(@NotNull Instance instance, @NotNull Chunk chunk) {
|
||||
Set<Chunk> chunks = getChunks(instance);
|
||||
chunks.remove(chunk);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@ -51,17 +46,19 @@ public class PerInstanceThreadProvider extends ThreadProvider {
|
||||
public List<Future<?>> update(long time) {
|
||||
List<Future<?>> futures = new ArrayList<>();
|
||||
|
||||
instanceChunkMap.forEach((instance, chunkIndexes) -> futures.add(pool.submit(() -> {
|
||||
instanceChunkMap.forEach((instance, chunks) -> futures.add(pool.submit(() -> {
|
||||
// Tick instance
|
||||
updateInstance(instance, time);
|
||||
// Tick chunks
|
||||
chunkIndexes.forEach((long chunkIndex) -> processChunkTick(instance, chunkIndex, time));
|
||||
for (Chunk chunk : chunks) {
|
||||
processChunkTick(instance, chunk, time);
|
||||
}
|
||||
})));
|
||||
return futures;
|
||||
}
|
||||
|
||||
private LongSet getChunkCoordinates(Instance instance) {
|
||||
return instanceChunkMap.computeIfAbsent(instance, inst -> new LongArraySet());
|
||||
private Set<Chunk> getChunks(Instance instance) {
|
||||
return instanceChunkMap.computeIfAbsent(instance, inst -> ConcurrentHashMap.newKeySet());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package net.minestom.server.thread;
|
||||
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
@ -33,12 +32,12 @@ public class SingleThreadProvider extends ThreadProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkLoad(@NotNull Instance instance, int chunkX, int chunkZ) {
|
||||
public void onChunkLoad(@NotNull Instance instance, @NotNull Chunk chunk) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkUnload(@NotNull Instance instance, int chunkX, int chunkZ) {
|
||||
public void onChunkUnload(@NotNull Instance instance, @NotNull Chunk chunk) {
|
||||
|
||||
}
|
||||
|
||||
@ -49,8 +48,7 @@ public class SingleThreadProvider extends ThreadProvider {
|
||||
for (Instance instance : instances) {
|
||||
updateInstance(instance, time);
|
||||
for (Chunk chunk : instance.getChunks()) {
|
||||
final long index = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ());
|
||||
processChunkTick(instance, index, time);
|
||||
processChunkTick(instance, chunk, time);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
@ -61,19 +61,17 @@ public abstract class ThreadProvider {
|
||||
* Be aware that this is possible for an instance to load chunks before being registered.
|
||||
*
|
||||
* @param instance the instance of the chunk
|
||||
* @param chunkX the chunk X
|
||||
* @param chunkZ the chunk Z
|
||||
* @param chunk the loaded chunk
|
||||
*/
|
||||
public abstract void onChunkLoad(@NotNull Instance instance, int chunkX, int chunkZ);
|
||||
public abstract void onChunkLoad(@NotNull Instance instance, @NotNull Chunk chunk);
|
||||
|
||||
/**
|
||||
* Called when a chunk is unloaded.
|
||||
*
|
||||
* @param instance the instance of the chunk
|
||||
* @param chunkX the chunk X
|
||||
* @param chunkZ the chunk Z
|
||||
* @param chunk the unloaded chunk
|
||||
*/
|
||||
public abstract void onChunkUnload(@NotNull Instance instance, int chunkX, int chunkZ);
|
||||
public abstract void onChunkUnload(@NotNull Instance instance, @NotNull Chunk chunk);
|
||||
|
||||
/**
|
||||
* Performs a server tick for all chunks based on their linked thread.
|
||||
@ -115,20 +113,15 @@ public abstract class ThreadProvider {
|
||||
/**
|
||||
* Processes a whole tick for a chunk.
|
||||
*
|
||||
* @param instance the instance of the chunk
|
||||
* @param chunkIndex the index of the chunk {@link ChunkUtils#getChunkIndex(int, int)}
|
||||
* @param time the time of the update in milliseconds
|
||||
* @param instance the instance of the chunk
|
||||
* @param chunk the chunk to update
|
||||
* @param time the time of the update in milliseconds
|
||||
*/
|
||||
protected void processChunkTick(@NotNull Instance instance, long chunkIndex, long time) {
|
||||
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
|
||||
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
|
||||
|
||||
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
|
||||
protected void processChunkTick(@NotNull Instance instance, @NotNull Chunk chunk, long time) {
|
||||
if (!ChunkUtils.isLoaded(chunk))
|
||||
return;
|
||||
|
||||
updateChunk(instance, chunk, time);
|
||||
|
||||
updateEntities(instance, chunk, time);
|
||||
}
|
||||
|
||||
@ -221,13 +214,15 @@ public abstract class ThreadProvider {
|
||||
*/
|
||||
protected void conditionalEntityUpdate(@NotNull Instance instance, @NotNull Chunk chunk, long time,
|
||||
@Nullable EntityValidator condition) {
|
||||
final Set<Entity> entities = instance.getChunkEntities(chunk);
|
||||
if (!instance.getEntities().isEmpty()) {
|
||||
final Set<Entity> entities = instance.getChunkEntities(chunk);
|
||||
|
||||
if (!entities.isEmpty()) {
|
||||
for (Entity entity : entities) {
|
||||
if (condition != null && !condition.isValid(entity))
|
||||
continue;
|
||||
entity.tick(time);
|
||||
if (!entities.isEmpty()) {
|
||||
for (Entity entity : entities) {
|
||||
if (condition != null && !condition.isValid(entity))
|
||||
continue;
|
||||
entity.tick(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user