WIP dynamic chunk thread change

This commit is contained in:
TheMode 2021-04-18 06:29:44 +02:00
parent 2e4a443bec
commit e9f13d0a82
6 changed files with 100 additions and 58 deletions

View File

@ -8,6 +8,7 @@ import net.minestom.server.lock.Acquisition;
import net.minestom.server.monitoring.TickMonitor;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.player.NettyPlayerConnection;
import net.minestom.server.thread.PerChunkThreadProvider;
import net.minestom.server.thread.PerInstanceThreadProvider;
import net.minestom.server.thread.ThreadProvider;
import net.minestom.server.utils.async.AsyncUtils;
@ -39,8 +40,8 @@ public final class UpdateManager {
{
// DEFAULT THREAD PROVIDER
threadProvider = new PerInstanceThreadProvider(4);
//threadProvider = new PerChunkThreadProvider(4);
//threadProvider = new PerInstanceThreadProvider(4);
threadProvider = new PerChunkThreadProvider(4);
}
/**

View File

@ -45,7 +45,6 @@ import net.minestom.server.item.Material;
import net.minestom.server.item.metadata.WrittenBookMeta;
import net.minestom.server.listener.PlayerDiggingListener;
import net.minestom.server.lock.Acquirable;
import net.minestom.server.lock.AcquirableCollection;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.ConnectionState;
import net.minestom.server.network.PlayerProvider;
@ -330,15 +329,16 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
//System.out.println(getAcquiredElement().getHandler().getBatchThread());
Collection<Acquirable<Player>> players = new ArrayList<>();
//if (username.equals("TheMode911"))
/*for (Player p1 : MinecraftServer.getConnectionManager().getOnlinePlayers()) {
for (Player p1 : MinecraftServer.getConnectionManager().getOnlinePlayers()) {
//players.add(p1.getAcquiredElement());
p1.getAcquiredElement().acquire(o -> {
for (Player p2 : MinecraftServer.getConnectionManager().getOnlinePlayers())
//System.out.println(getUsername()+": "+o+" "+getAliveTicks());
/*for (Player p2 : MinecraftServer.getConnectionManager().getOnlinePlayers())
p2.getAcquiredElement().acquire(o2 -> {
//System.out.println(getAcquiredElement().getHandler().getBatchThread().monitor.isOccupiedByCurrentThread());
});
});*/
});
}*/
}
/*AcquirableCollection<Player> players1 = new AcquirableCollection<>(players);
players1.forEach(player -> {

View File

@ -54,7 +54,7 @@ import java.util.function.Consumer;
* WARNING: when making your own implementation registering the instance manually is required
* with {@link InstanceManager#registerInstance(Instance)}, and
* you need to be sure to signal the {@link UpdateManager} of the changes using
* {@link UpdateManager#signalChunkLoad(Instance, Chunk)} and {@link UpdateManager#signalChunkUnload(Instance, Chunk)}.
* {@link UpdateManager#signalChunkLoad(Chunk)} and {@link UpdateManager#signalChunkUnload(Chunk)}.
*/
public abstract class Instance implements BlockModifier, Tickable, EventHandler, DataContainer, PacketGroupingAudience {
@ -253,7 +253,7 @@ public abstract class Instance implements BlockModifier, Tickable, EventHandler,
* Used when a {@link Chunk} is not currently loaded in memory and need to be retrieved from somewhere else.
* Could be read from disk, or generated from scratch.
* <p>
* Be sure to signal the chunk using {@link UpdateManager#signalChunkLoad(Instance, Chunk)} and to cache
* Be sure to signal the chunk using {@link UpdateManager#signalChunkLoad(Chunk)} and to cache
* that this chunk has been loaded.
* <p>
* WARNING: it has to retrieve a chunk, this is not optional and should execute the callback in all case.
@ -267,7 +267,7 @@ public abstract class Instance implements BlockModifier, Tickable, EventHandler,
/**
* Called to generated a new {@link Chunk} from scratch.
* <p>
* Be sure to signal the chunk using {@link UpdateManager#signalChunkLoad(Instance, Chunk)} and to cache
* Be sure to signal the chunk using {@link UpdateManager#signalChunkLoad(Chunk)} and to cache
* that this chunk has been loaded.
* <p>
* This is where you can put your chunk generation code.

View File

@ -1,8 +1,8 @@
package net.minestom.server.lock;
import net.minestom.server.entity.Entity;
import net.minestom.server.instance.Chunk;
import net.minestom.server.thread.BatchThread;
import net.minestom.server.thread.ThreadProvider;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@ -61,21 +61,19 @@ public interface Acquirable<T> {
class Handler {
private volatile BatchThread batchThread;
private volatile Chunk batchChunk;
private volatile ThreadProvider.ChunkEntry chunkEntry;
public BatchThread getBatchThread() {
return batchThread;
}
public Chunk getBatchChunk() {
return batchChunk;
public ThreadProvider.ChunkEntry getChunkEntry() {
return chunkEntry;
}
@ApiStatus.Internal
public void refreshBatchInfo(@NotNull BatchThread batchThread, @NotNull Chunk batchChunk) {
this.batchThread = batchThread;
this.batchChunk = batchChunk;
public void refreshChunkEntry(@NotNull ThreadProvider.ChunkEntry chunkEntry) {
this.chunkEntry = chunkEntry;
}
public BatchThread getBatchThread() {
return chunkEntry != null ? chunkEntry.getThread() : null;
}
}

View File

@ -1,10 +1,12 @@
package net.minestom.server.thread;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Entity;
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.lock.Acquirable;
import net.minestom.server.utils.chunk.ChunkUtils;
import org.jetbrains.annotations.NotNull;
import java.util.*;
@ -22,6 +24,8 @@ public abstract class ThreadProvider {
private final Map<BatchThread, Set<ChunkEntry>> threadChunkMap = new HashMap<>();
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> removedEntities = ConcurrentHashMap.newKeySet();
public ThreadProvider(int threadCount) {
@ -67,7 +71,8 @@ public abstract class ThreadProvider {
ChunkEntry chunkEntry = new ChunkEntry(thread, chunk);
chunks.add(chunkEntry);
chunkEntryMap.put(chunk, chunkEntry);
this.chunkEntryMap.put(chunk, chunkEntry);
this.chunks.add(chunk);
}
protected void removeChunk(Chunk chunk) {
@ -80,6 +85,7 @@ public abstract class ThreadProvider {
}
chunkEntryMap.remove(chunk);
}
this.chunks.remove(chunk);
}
/**
@ -105,7 +111,10 @@ public abstract class ThreadProvider {
.collect(Collectors.toList());
Acquirable.refreshEntities(Collections.unmodifiableList(entities));
chunkEntries.forEach(chunkEntry -> {
chunkEntry.chunk.tick(time);
Chunk chunk = chunkEntry.chunk;
if (!ChunkUtils.isLoaded(chunk))
return;
chunk.tick(time);
chunkEntry.entities.forEach(entity -> {
thread.monitor.enter();
entity.tick(time);
@ -122,48 +131,44 @@ public abstract class ThreadProvider {
{
for (Entity entity : removedEntities) {
Acquirable<Entity> acquirable = entity.getAcquiredElement();
Chunk batchChunk = acquirable.getHandler().getBatchChunk();
ChunkEntry chunkEntry = acquirable.getHandler().getChunkEntry();
// Remove from list
{
ChunkEntry chunkEntry = chunkEntryMap.get(batchChunk);
if (chunkEntry != null) {
chunkEntry.entities.remove(entity);
}
if (chunkEntry != null) {
chunkEntry.entities.remove(entity);
}
}
this.removedEntities.clear();
}
// Update as many entities as possible
// TODO: incremental update instead of full
for (Instance instance : MinecraftServer.getInstanceManager().getInstances()) {
var entities = instance.getEntities();
for (Entity entity : entities) {
Acquirable<Entity> acquirable = entity.getAcquiredElement();
Chunk batchChunk = acquirable.getHandler().getBatchChunk();
Chunk entityChunk = entity.getChunk();
if (!Objects.equals(batchChunk, entityChunk)) {
// Entity is possibly not in the correct thread
// Remove from previous list
{
ChunkEntry chunkEntry = chunkEntryMap.get(batchChunk);
if (chunkEntry != null) {
chunkEntry.entities.remove(entity);
}
}
int size = chunks.size();
int counter = 0;
// TODO incremental update, maybe a percentage of the tick time?
while (counter++ < size) {
Chunk chunk = chunks.pollFirst();
if (!ChunkUtils.isLoaded(chunk)) {
removeChunk(chunk);
return;
}
// Add to new list
{
ChunkEntry chunkEntry = chunkEntryMap.get(entityChunk);
if (chunkEntry != null) {
chunkEntry.entities.add(entity);
acquirable.getHandler().refreshBatchInfo(chunkEntry.thread, chunkEntry.chunk);
}
// Update chunk threads
{
// TODO
}
// Update entities
{
Instance instance = chunk.getInstance();
refreshEntitiesThread(instance, chunk);
if (instance instanceof InstanceContainer) {
for (SharedInstance sharedInstance : ((InstanceContainer) instance).getSharedInstances()) {
refreshEntitiesThread(sharedInstance, chunk);
}
}
}
// Add back to the deque
chunks.addLast(chunk);
}
}
@ -179,7 +184,37 @@ public abstract class ThreadProvider {
return threads;
}
private static class ChunkEntry {
private void refreshEntitiesThread(Instance instance, Chunk chunk) {
var entities = instance.getChunkEntities(chunk);
for (Entity entity : entities) {
Acquirable<Entity> acquirable = entity.getAcquiredElement();
ChunkEntry handlerChunkEntry = acquirable.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);
}
}
// Add to new list
{
ChunkEntry chunkEntry = chunkEntryMap.get(entityChunk);
if (chunkEntry != null) {
chunkEntry.entities.add(entity);
acquirable.getHandler().refreshChunkEntry(chunkEntry);
}
}
}
}
}
public static class ChunkEntry {
private final BatchThread thread;
private final Chunk chunk;
private final List<Entity> entities = new ArrayList<>();
@ -189,6 +224,14 @@ public abstract class ThreadProvider {
this.chunk = chunk;
}
public @NotNull BatchThread getThread() {
return thread;
}
public @NotNull Chunk getChunk() {
return chunk;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@ -292,7 +292,7 @@ public class PlayerInit {
// Unload the chunk (save memory) if it has no remaining viewer
if (chunk.getViewers().isEmpty()) {
//player.getInstance().unloadChunk(chunk);
player.getInstance().unloadChunk(chunk);
}
});
}