Skip to content

Commit

Permalink
Fix SOFE when many chunks are loaded
Browse files Browse the repository at this point in the history
  • Loading branch information
senseiwells committed Oct 16, 2024
1 parent af8bf17 commit f3a0620
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 37 deletions.
21 changes: 2 additions & 19 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
java
}

val modVersion = "2.0.1"
val modVersion = "2.0.2"
val releaseVersion = "${modVersion}+mc${libs.versions.minecraft.get()}"
version = releaseVersion
group = "me.senseiwells"
Expand Down Expand Up @@ -60,24 +60,7 @@ tasks {
"""
## ChunkDebug $modVersion
ChunkDebug has been completely re-written from scratch.
### Changes
- Complete network and rendering overhaul
- No longer requires EssentialClient on the client, install ChunkDebug on the client instead
- Scheduled chunk unloading is now synchronized
- Added support for permission mods to control who can use ChunkDebug
- New GUI
- Updated the GUI to be more user friendly
- You an now set a Chunk Retention to determine how long to keep unloaded chunks rendered for
- You can now render the minimap on top of the chunk debug screen to allow you to line things up easier
- You can disable rendering of the chunk generation stages and/or ticket types
- You can hide both the chunk debug settings and/or the chunk breakdown by hitting F1 or by clicking the toggle buttons in the bottom corners
- You can now select regions of chunks by dragging right click
- General QOL, the gui now doesn't behave weirdly when zooming and correctly centers when jumping to clusters, and the gui isn't grid based any more, so panning is smoother
- Bug Fixes
- Chunk Stages are now synchronized correctly with the client
- "Inaccessible" chunks are displayed correctly now
Fixed a crash when thousands of chunks were loaded.
""".trimIndent()
)
type = STABLE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import me.senseiwells.chunkdebug.client.gui.ChunkDebugScreen;
import me.senseiwells.chunkdebug.common.network.*;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
Expand Down Expand Up @@ -44,6 +45,7 @@ public void onInitializeClient() {
KeyBindingHelper.registerKeyBinding(this.keybind);

ClientTickEvents.END_CLIENT_TICK.register(this::onClientTick);
ClientLifecycleEvents.CLIENT_STOPPING.register(this::onClientStopping);

ClientPlayNetworking.registerGlobalReceiver(HelloPayload.TYPE, this::handleHello);
ClientPlayNetworking.registerGlobalReceiver(ByePayload.TYPE, this::handleBye);
Expand Down Expand Up @@ -88,9 +90,13 @@ private void onClientTick(Minecraft minecraft) {
}
}

private void onClientStopping(Minecraft minecraft) {
this.setScreen(null);
}

private void handleHello(HelloPayload payload, ClientPlayNetworking.Context context) {
if (payload.version() == ChunkDebug.PROTOCOL_VERSION) {
this.screen = new ChunkDebugScreen();
this.setScreen(new ChunkDebugScreen());
ChunkDebug.LOGGER.info("ChunkDebug connection successful");
} else if (payload.version() < ChunkDebug.PROTOCOL_VERSION) {
ChunkDebug.LOGGER.info("ChunkDebug failed to connect, server is out of date!");
Expand All @@ -104,7 +110,7 @@ private void handleBye(ByePayload payload, ClientPlayNetworking.Context context)
if (minecraft.screen == this.screen) {
minecraft.setScreen(null);
}
this.screen = null;
this.setScreen(null);
}

private void handleChunkData(ChunkDataPayload payload, ClientPlayNetworking.Context context) {
Expand All @@ -126,4 +132,11 @@ private void trySendPayload(Supplier<CustomPacketPayload> supplier) {
listener.send(new ServerboundCustomPayloadPacket(supplier.get()));
}
}

private void setScreen(@Nullable ChunkDebugScreen screen) {
if (this.screen != null) {
this.screen.shutdown();
}
this.screen = screen;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package me.senseiwells.chunkdebug.client.gui;

import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.longs.LongStack;
import net.minecraft.world.level.ChunkPos;

import java.util.ArrayList;
Expand Down Expand Up @@ -119,12 +121,18 @@ private static List<LongSet> search(long origin, LongSet originGroup) {
}

private static void searchFrom(long position, LongSet group, LongSet checked, LongSet found) {
long[] directions = getOffsets(position);
LongStack stack = new LongArrayList();
stack.push(position);

for (long direction : directions) {
if (checked.add(direction) && group.contains(direction)) {
found.add(direction);
searchFrom(direction, group, checked, found);
while (!stack.isEmpty()) {
long current = stack.popLong();
long[] directions = getOffsets(current);

for (long direction : directions) {
if (checked.add(direction) && group.contains(direction)) {
found.add(direction);
stack.push(direction);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
import me.senseiwells.chunkdebug.client.ChunkDebugClient;
import me.senseiwells.chunkdebug.client.gui.widget.ArrowButton;
import me.senseiwells.chunkdebug.client.gui.widget.ArrowButton.Direction;
Expand All @@ -30,6 +31,10 @@
import org.lwjgl.glfw.GLFW;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static me.senseiwells.chunkdebug.client.utils.RenderUtils.*;

Expand All @@ -43,6 +48,8 @@ public class ChunkDebugScreen extends Screen {

private static final int MENU_PADDING = 3;

private final ExecutorService executor = Executors.newSingleThreadExecutor();

private final Map<ResourceKey<Level>, DimensionState> states = new Object2ObjectOpenHashMap<>();
private final List<ResourceKey<Level>> dimensions = new ArrayList<>();
private int dimensionWidth = 0;
Expand Down Expand Up @@ -203,6 +210,10 @@ public void removed() {
}
}

public void shutdown() {
this.executor.shutdown();
}

@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partial) {
this.renderBlurredBackground(partial);
Expand Down Expand Up @@ -576,21 +587,22 @@ private DimensionState state() {

private DimensionState state(ResourceKey<Level> dimension) {
return this.states.computeIfAbsent(dimension, dim -> {
DimensionState state = new DimensionState(dim);
DimensionState state = new DimensionState(dim, this.executor);
this.initializeState(state);
return state;
});
}

private void jumpToCluster(int offset) {
DimensionState state = this.state();
this.clusterIndex = (this.clusterIndex + offset + state.clusters.count()) % state.clusters.count();
LongSet cluster = state.clusters.getCluster(this.clusterIndex);
List<ChunkPos> positions = cluster.longStream().mapToObj(ChunkPos::new).toList();
ChunkSelection selection = ChunkSelection.fromPositions(positions);
this.setMapCenter(selection.getCenterChunkPos());
this.clusterSelection = selection;
this.clusterTicks = 20;
state.getCluster(this.clusterIndex + offset).thenApplyAsync(pair -> {
ChunkSelection selection = pair.first();
this.clusterIndex = pair.secondInt();
this.setMapCenter(selection.getCenterChunkPos());
this.clusterSelection = selection;
this.clusterTicks = 20;
return null;
}, this.minecraft);
}

private void loadDimensions() {
Expand Down Expand Up @@ -711,12 +723,15 @@ public Component pretty() {
}

private static class DimensionState {

private final Multimap<Integer, ChunkData> unloaded = HashMultimap.create();

private final Long2ObjectMap<ChunkData> chunks = new Long2ObjectOpenHashMap<>();
private final ChunkClusters clusters = new ChunkClusters();
private final ResourceKey<Level> dimension;

private final Executor clusterWorker;

private ChunkSelection selection;
private ChunkPos first;

Expand All @@ -727,22 +742,36 @@ private static class DimensionState {

boolean initialized = false;

private DimensionState(ResourceKey<Level> dimension) {
private DimensionState(ResourceKey<Level> dimension, Executor clusterWorker) {
this.dimension = dimension;
this.clusterWorker = clusterWorker;
}

void add(long pos, ChunkData data) {
this.chunks.put(pos, data);
this.clusters.add(pos);
this.clusterWorker.execute(() -> {
this.clusters.add(pos);
});
}

void remove(long pos, int tick) {
this.clusters.remove(pos);
this.clusterWorker.execute(() -> {
this.clusters.remove(pos);
});
ChunkData data = this.chunks.remove(pos);
if (data != null) {
this.unloaded.put(tick, data);
data.updateUnloading(false);
}
}

CompletableFuture<ObjectIntPair<ChunkSelection>> getCluster(int index) {
return CompletableFuture.supplyAsync(() -> {
int corrected = index + this.clusters.count() % this.clusters.count();
LongSet cluster = this.clusters.getCluster(corrected);
List<ChunkPos> positions = cluster.longStream().mapToObj(ChunkPos::new).toList();
return ObjectIntPair.of(ChunkSelection.fromPositions(positions), corrected);
}, this.clusterWorker);
}
}
}

0 comments on commit f3a0620

Please sign in to comment.