Skip to content
This repository has been archived by the owner on Nov 30, 2021. It is now read-only.

Commit

Permalink
separate tracking of chunks for full and chunks for generation
Browse files Browse the repository at this point in the history
  • Loading branch information
Gegy committed Sep 19, 2020
1 parent 69fb991 commit a7ae020
Show file tree
Hide file tree
Showing 17 changed files with 399 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public <T> ChunkTask<T> spawn(ChunkEntry entry, Future<T> future) {

@Override
public <T> void enqueue(ChunkTask<T> task) {
this.queue.enqueue(task, task.holder.getLevel());
this.queue.enqueue(task, task.getLevel());
}

public void run() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public ChunkMainThreadExecutor(ThreadExecutor<Runnable> executor) {

@Override
public <T> void enqueue(ChunkTask<T> task) {
this.queue.enqueue(task, task.holder.getLevel());
this.queue.enqueue(task, task.getLevel());
this.tryEnqueue();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import net.gegy1000.tictacs.TicTacs;
import net.minecraft.server.world.ChunkHolder;

import javax.annotation.Nullable;

public final class ChunkTask<T> {
public final ChunkHolder holder;

Expand All @@ -14,7 +16,7 @@ public final class ChunkTask<T> {

private volatile boolean complete;

ChunkTask(ChunkHolder holder, Future<T> future, TaskQueue queue) {
ChunkTask(@Nullable ChunkHolder holder, Future<T> future, TaskQueue queue) {
this.holder = holder;
this.future = future;
this.queue = queue;
Expand Down Expand Up @@ -51,4 +53,8 @@ void enqueue() {
public boolean isComplete() {
return this.complete;
}

public int getLevel() {
return this.holder != null ? this.holder.getLevel() : 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@

public interface TaskSpawner {
<T> ChunkTask<T> spawn(ChunkEntry entry, Future<T> future);

default <T> ChunkTask<T> spawn(Future<T> future) {
return this.spawn(null, future);
}
}
4 changes: 4 additions & 0 deletions src/main/java/net/gegy1000/tictacs/chunk/ChunkController.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,9 @@ default ThreadedAnvilChunkStorage asTacs() {

<T> void spawnOnMainThread(ChunkEntry entry, Future<T> future);

default <T> void spawnOnMainThread(Future<T> future) {
this.spawnOnMainThread(null, future);
}

void spawnOnMainThread(ChunkEntry entry, Runnable runnable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public final class ChunkLevelTracker {
public static final int FULL_LEVEL = TicTacsConfig.get().maxViewDistance + 1;
public static final int MAX_LEVEL = FULL_LEVEL + ChunkStep.getMaxDistance() + 1;

public static final int LIGHT_TICKET_LEVEL = FULL_LEVEL + ChunkStep.getDistanceFromFull(ChunkStep.LIGHTING.getPrevious());
public static final int LIGHT_TICKET_LEVEL = FULL_LEVEL + ChunkStep.getDistanceFromFull(ChunkStep.GENERATION);

private final ServerWorld world;
private final ChunkController controller;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@
public final class AwaitAll<T> implements Future<Unit> {
private final Future<T>[] futures;

public AwaitAll(Future<T>[] futures) {
private AwaitAll(Future<T>[] futures) {
this.futures = futures;
}

public static <T> AwaitAll<T> of(Future<T>[] futures) {
return new AwaitAll<>(futures);
}

@Nullable
@Override
public Unit poll(Waker waker) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public final class ChunkStep {
})
.loadAsync(ChunkStep::makeFull);

public static final ChunkStep GENERATION = ChunkStep.LIGHTING.getPrevious();
public static final ChunkStep MIN_WITH_LOAD_TASK = ChunkStep.LIGHTING;

private static final ChunkStep[] STATUS_TO_STEP;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package net.gegy1000.tictacs.chunk.ticket;

import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongList;
import it.unimi.dsi.fastutil.longs.LongLists;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.gegy1000.justnow.future.Future;
import net.gegy1000.justnow.tuple.Unit;
import net.gegy1000.tictacs.chunk.ChunkAccess;
import net.gegy1000.tictacs.chunk.ChunkController;
import net.gegy1000.tictacs.chunk.ChunkLevelTracker;
import net.gegy1000.tictacs.chunk.entry.ChunkEntry;
import net.gegy1000.tictacs.chunk.entry.ChunkListener;
import net.gegy1000.tictacs.chunk.future.AwaitAll;
import net.gegy1000.tictacs.chunk.step.ChunkStep;
import net.gegy1000.tictacs.chunk.upgrade.ChunkUpgrader;
import net.minecraft.server.world.ChunkTicketManager;
import net.minecraft.server.world.ChunkTicketType;
import net.minecraft.util.math.ChunkPos;

// TODO: ideally we overwrite generation tickets with player tickets
public final class PlayerTicketManager implements TicketTracker {
private final ChunkController controller;
private final ChunkStep step;
private final ChunkTicketType<ChunkPos> ticketType;
private final int ticketLevel;
private final int levelStep;

private final int levelCount;
private final LongSet[] queues;
private final int[] waitingCounts;

private int currentLevel;

private final LongList addedChunks = new LongArrayList();

public PlayerTicketManager(ChunkController controller, ChunkStep step, int radius, ChunkTicketType<ChunkPos> ticketType, int levelStep) {
this.controller = controller;
this.step = step;
this.ticketType = ticketType;
this.levelStep = levelStep;

this.ticketLevel = ChunkLevelTracker.FULL_LEVEL + ChunkStep.getDistanceFromFull(step) - radius;

this.levelCount = (ChunkLevelTracker.FULL_LEVEL + levelStep - 1) / levelStep;
this.queues = new LongSet[this.levelCount];
this.waitingCounts = new int[this.levelCount];

for (int i = 0; i < this.levelCount; i++) {
this.queues[i] = new LongOpenHashSet();
}
}

public LongList collectTickets() {
ChunkTicketManager ticketManager = this.controller.getTicketManager();

while (true) {
int currentLevel = this.currentLevel;
if (currentLevel >= this.levelCount) {
return LongLists.EMPTY_LIST;
}

LongSet queue = this.queues[currentLevel];

// once this level is totally completed, we can advance to the next level
if (queue.isEmpty() && this.waitingCounts[currentLevel] <= 0) {
this.currentLevel = this.getNextLevel(currentLevel);
continue;
}

if (!queue.isEmpty()) {
LongList addedChunks = this.addedChunks;
addedChunks.clear();

addedChunks.addAll(queue);
queue.clear();

LongIterator iterator = addedChunks.iterator();
while (iterator.hasNext()) {
ChunkPos pos = new ChunkPos(iterator.nextLong());
ticketManager.addTicketWithLevel(this.ticketType, pos, this.ticketLevel, pos);
}

return addedChunks;
}

return LongLists.EMPTY_LIST;
}
}

public void waitForChunks(LongList chunks) {
if (chunks.isEmpty()) {
return;
}

int count = chunks.size();

int currentLevel = this.currentLevel;
this.waitingCounts[currentLevel] += count;

Future<Unit> future = this.awaitAllChunks(chunks);
this.controller.spawnOnMainThread(future.map(unit -> {
int waitingCount = this.waitingCounts[currentLevel];
this.waitingCounts[currentLevel] = Math.max(waitingCount - count, 0);
return unit;
}));

chunks.clear();
}

private Future<Unit> awaitAllChunks(LongList entries) {
ChunkUpgrader upgrader = this.controller.getUpgrader();
ChunkAccess chunks = this.controller.getMap().visible();

ChunkListener[] listeners = new ChunkListener[entries.size()];

for (int i = 0; i < entries.size(); i++) {
long pos = entries.getLong(i);
ChunkEntry entry = chunks.getEntry(pos);
if (entry == null) {
throw new IllegalStateException("missing entry added by player ticket");
}

upgrader.spawnUpgradeTo(entry, this.step);
listeners[i] = entry.getListenerFor(this.step);
}

return AwaitAll.of(listeners);
}

@Override
public void enqueueTicket(long pos, int distance) {
int level = distance / this.levelStep;
if (level >= this.levelCount) {
return;
}

this.queues[level].add(pos);
if (level < this.currentLevel) {
this.currentLevel = level;
}
}

@Override
public void removeTicket(long pos) {
this.removeChunkTicket(pos);

for (int i = this.currentLevel; i < this.levelCount; i++) {
LongSet queue = this.queues[i];
if (queue.remove(pos)) {
if (queue.isEmpty()) {
this.removeLevel(i);
}
return;
}
}
}

private void removeChunkTicket(long posKey) {
ChunkPos pos = new ChunkPos(posKey);
this.controller.getTicketManager().removeTicketWithLevel(this.ticketType, pos, this.ticketLevel, pos);
}

@Override
public void moveTicket(long pos, int fromDistance, int toDistance) {
int fromLevel = fromDistance / this.levelStep;
if (fromLevel >= this.levelCount) {
return;
}

LongSet fromQueue = this.queues[fromLevel];
if (!fromQueue.remove(pos)) {
return;
}

this.enqueueTicket(pos, toDistance);

if (fromQueue.isEmpty()) {
this.removeLevel(fromLevel);
}
}

private void removeLevel(int level) {
this.waitingCounts[level] = 0;
if (level == this.currentLevel) {
this.currentLevel = this.getNextLevel(this.currentLevel);
}
}

private int getNextLevel(int fromLevel) {
for (int i = fromLevel; i < this.levelCount; i++) {
if (!this.queues[i].isEmpty()) {
return i;
}
}
return this.levelCount;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package net.gegy1000.tictacs.chunk.ticket;

public interface TicketTracker {
void enqueueTicket(long pos, int distance);

void removeTicket(long pos);

void moveTicket(long pos, int fromDistance, int toDistance);
}
19 changes: 11 additions & 8 deletions src/main/java/net/gegy1000/tictacs/chunk/tracker/ChunkTracker.java
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ public void removePlayer(ServerPlayerEntity player) {
}

@Override
public void onRemoveChunk(ChunkEntry entry) {
public void onAddChunk(ChunkEntry entry) {
ChunkPos chunkPos = entry.getPos();
long chunkKey = chunkPos.toLong();

Expand All @@ -285,15 +285,16 @@ public void onRemoveChunk(ChunkEntry entry) {
continue;
}

ChunkTrackWatcher.Function stopTracking = tracker.getStopTracking();
if (stopTracking != null) {
stopTracking.accept(player, chunkKey, entry);
ChunkTrackWatcher.Function startTracking = tracker.getStartTracking();
if (startTracking != null) {
startTracking.accept(player, chunkKey, entry);
}
}
}
}

public void onChunkFull(ChunkEntry entry, WorldChunk chunk) {
@Override
public void onRemoveChunk(ChunkEntry entry) {
ChunkPos chunkPos = entry.getPos();
long chunkKey = chunkPos.toLong();

Expand All @@ -305,13 +306,15 @@ public void onChunkFull(ChunkEntry entry, WorldChunk chunk) {
continue;
}

ChunkTrackWatcher.Function startTracking = tracker.getStartTracking();
if (startTracking != null) {
startTracking.accept(player, chunkKey, entry);
ChunkTrackWatcher.Function stopTracking = tracker.getStopTracking();
if (stopTracking != null) {
stopTracking.accept(player, chunkKey, entry);
}
}
}
}

public void onChunkFull(ChunkEntry entry, WorldChunk chunk) {
ChunkPackets.Data data = ChunkPackets.dataFor(chunk);
ChunkPackets.Entities entities = ChunkPackets.entitiesFor(entry);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public final class ChunkUpgrader {
private final StructureManager structures;
private final ServerLightingProvider lighting;

public final Semaphore lightingThrottler = new Semaphore(16);
public final Semaphore lightingThrottler = new Semaphore(24);

public ChunkUpgrader(
ServerWorld world,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@

@Mixin(ChunkTicketManager.class)
public class ChunkTicketManagerMixin {
@ModifyConstant(method = "<init>", constant = @Constant(intValue = 4))
private int getMaxTasks(int maxTasks) {
return 128;
}

@ModifyConstant(method = "<init>", constant = @Constant(intValue = 33))
private int getFullLevelForNearbyChunkTicketUpdater(int level) {
return ChunkLevelTracker.FULL_LEVEL;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public Future<Unit> getRadiusAs(ChunkPos pos, int radius, ChunkStep step) {

flushListener.invalidateWaker();

return new AwaitAll<>(futures);
return AwaitAll.of(futures);
}

@Override
Expand Down
Loading

0 comments on commit a7ae020

Please sign in to comment.