Skip to content

Commit 2e0fa6b

Browse files
committed
Fix World Map FPS, Fix Shutdown
1 parent caf216a commit 2e0fa6b

5 files changed

Lines changed: 96 additions & 15 deletions

File tree

common/src/main/java/com/mamiyaotaru/voxelmap/VoxelConstants.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,21 @@ public static void onShutDown() {
150150
VoxelConstants.getVoxelMapInstance().getPersistentMap().purgeCachedRegions();
151151
VoxelConstants.getVoxelMapInstance().getMapOptions().saveAll();
152152
BiomeRepository.saveBiomeColors();
153-
long shutdownTime = System.currentTimeMillis();
154-
155-
while (ThreadManager.executorService.getQueue().size() + ThreadManager.executorService.getActiveCount() > 0 && System.currentTimeMillis() - shutdownTime < 10000L) {
156-
Thread.onSpinWait();
153+
long shutdownStart = System.currentTimeMillis();
154+
final long maxWaitMs = 500L;
155+
while (ThreadManager.executorService.getQueue().size() + ThreadManager.executorService.getActiveCount() > 0
156+
&& System.currentTimeMillis() - shutdownStart < maxWaitMs) {
157+
try {
158+
Thread.sleep(10L);
159+
} catch (InterruptedException e) {
160+
Thread.currentThread().interrupt();
161+
VoxelConstants.getLogger().warn("Interrupted while waiting for world map tasks during shutdown; continuing.");
162+
break;
163+
}
164+
}
165+
int remaining = ThreadManager.executorService.getQueue().size() + ThreadManager.executorService.getActiveCount();
166+
if (remaining > 0) {
167+
VoxelConstants.getLogger().warn("Continuing shutdown with {} world map task(s) still pending.", remaining);
157168
}
158169
}
159170

common/src/main/java/com/mamiyaotaru/voxelmap/VoxelMap.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,11 @@ public void onClientStarted() {
412412
}
413413

414414
public void onClientStopping() {
415-
VoxelConstants.onShutDown();
415+
try {
416+
VoxelConstants.onShutDown();
417+
} catch (RuntimeException e) {
418+
VoxelConstants.getLogger().warn("Error during VoxelMap shutdown preparation; continuing.", e);
419+
}
416420
ThreadManager.flushSaveQueue();
417421
}
418422

common/src/main/java/com/mamiyaotaru/voxelmap/persistent/CachedRegion.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ private void loadCachedData() {
526526
private void saveData(boolean newThread) {
527527
if (this.liveChunksUpdated && !this.worldNamePathPart.isEmpty()) {
528528
if (newThread) {
529-
ThreadManager.saveExecutorService.execute(() -> {
529+
ThreadManager.submitSaveTask(() -> {
530530
if (VoxelConstants.DEBUG) {
531531
VoxelConstants.getLogger().info("Saving region file for " + CachedRegion.this.x + "," + CachedRegion.this.z + " in " + CachedRegion.this.worldNamePathPart + "/" + CachedRegion.this.subworldNamePathPart + CachedRegion.this.dimensionNamePathPart);
532532
}
@@ -543,7 +543,7 @@ private void saveData(boolean newThread) {
543543
VoxelConstants.getLogger().info("Finished saving region file for " + CachedRegion.this.x + "," + CachedRegion.this.z + " in " + CachedRegion.this.worldNamePathPart + "/" + CachedRegion.this.subworldNamePathPart + CachedRegion.this.dimensionNamePathPart + " ("
544544
+ ThreadManager.saveExecutorService.getQueue().size() + ")");
545545
}
546-
});
546+
}, "region " + this.x + "," + this.z + " (" + this.worldNamePathPart + "/" + this.subworldNamePathPart + this.dimensionNamePathPart + ")");
547547
} else {
548548
try {
549549
this.doSave();

common/src/main/java/com/mamiyaotaru/voxelmap/persistent/GuiPersistentMap.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,11 @@ private void drawNewOldChunkOverlayWorldMap(GuiGraphicsExtractor graphics, int l
13031303
if (!options.showNewOldChunks || !radarOptions.showNewerNewChunks) {
13041304
return;
13051305
}
1306+
// At extreme zoom-out these overlays are barely legible and can still add frame cost.
1307+
// Hard-stop rendering at and below 0.020x world map zoom.
1308+
if (this.zoom <= 0.020F) {
1309+
return;
1310+
}
13061311

13071312
int minChunkX = leftRegion * 16;
13081313
int maxChunkX = rightRegion * 16 + 15;

common/src/main/java/com/mamiyaotaru/voxelmap/persistent/ThreadManager.java

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@
33
import com.mamiyaotaru.voxelmap.VoxelConstants;
44
import java.util.concurrent.FutureTask;
55
import java.util.concurrent.LinkedBlockingQueue;
6+
import java.util.concurrent.RejectedExecutionException;
67
import java.util.concurrent.ThreadFactory;
78
import java.util.concurrent.ThreadPoolExecutor;
89
import java.util.concurrent.TimeUnit;
910
import java.util.concurrent.atomic.AtomicInteger;
1011
import org.jetbrains.annotations.NotNull;
1112

1213
public final class ThreadManager {
14+
private static final long SAVE_FLUSH_TIMEOUT_SECONDS = 5L;
1315
static final int concurrentThreads = Math.min(Math.max(Runtime.getRuntime().availableProcessors() / 2, 1), 4);
1416
static final LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
1517
public static final ThreadPoolExecutor executorService = new ThreadPoolExecutor(0, concurrentThreads, 60L, TimeUnit.SECONDS, queue);
16-
public static ThreadPoolExecutor saveExecutorService = new ThreadPoolExecutor(0, concurrentThreads, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
18+
public static ThreadPoolExecutor saveExecutorService = createSaveExecutor();
19+
private static volatile boolean saveShutdownInProgress;
20+
private static final AtomicInteger skippedSaveTasks = new AtomicInteger();
1721

1822
private ThreadManager() {}
1923

@@ -28,21 +32,78 @@ public static void emptyQueue() {
2832
}
2933

3034
public static void flushSaveQueue() {
31-
saveExecutorService.shutdown();
35+
ThreadPoolExecutor executor = saveExecutorService;
36+
saveShutdownInProgress = true;
37+
int queuedAtStart = executor.getQueue().size();
38+
int activeAtStart = executor.getActiveCount();
39+
VoxelConstants.getLogger().info("Flushing map save queue (queued: {}, active: {})", queuedAtStart, activeAtStart);
3240
try {
33-
while (!saveExecutorService.awaitTermination(240, TimeUnit.SECONDS)) {
34-
VoxelConstants.getLogger().info("Waiting for map save... (" + saveExecutorService.getQueue().size() + ")");
41+
executor.shutdown();
42+
if (!executor.awaitTermination(SAVE_FLUSH_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
43+
int queuedBeforeCancel = executor.getQueue().size();
44+
int cancelled = executor.shutdownNow().size();
45+
VoxelConstants.getLogger().warn("Map save flush timed out after {}s; cancelling remaining saves (queued before cancel: {}, cancelled: {})",
46+
SAVE_FLUSH_TIMEOUT_SECONDS, queuedBeforeCancel, cancelled);
3547
}
3648
} catch (InterruptedException e) {
3749
Thread.currentThread().interrupt();
50+
VoxelConstants.getLogger().warn("Interrupted while flushing map save queue; continuing shutdown.");
51+
} catch (RuntimeException e) {
52+
VoxelConstants.getLogger().warn("Unexpected error while flushing map save queue; continuing shutdown.", e);
3853
}
39-
saveExecutorService = new ThreadPoolExecutor(0, concurrentThreads, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
40-
VoxelConstants.getLogger().info("Save queue flushed!");
54+
int skipped = skippedSaveTasks.getAndSet(0);
55+
VoxelConstants.getLogger().info("Map save flush finished (terminated: {}, skipped submissions: {})", executor.isTerminated(), skipped);
56+
saveExecutorService = createSaveExecutor();
57+
saveShutdownInProgress = false;
58+
}
59+
60+
public static void submitSaveTask(Runnable task, String description) {
61+
if (task == null) {
62+
return;
63+
}
64+
ThreadPoolExecutor executor = saveExecutorService;
65+
if (saveShutdownInProgress || executor.isShutdown() || executor.isTerminated()) {
66+
int skipped = skippedSaveTasks.incrementAndGet();
67+
if (skipped <= 5 || VoxelConstants.DEBUG) {
68+
VoxelConstants.getLogger().debug("Skipping save task during shutdown: {} (skipped count: {})", description, skipped);
69+
}
70+
return;
71+
}
72+
73+
Runnable guarded = () -> {
74+
try {
75+
task.run();
76+
} catch (RuntimeException e) {
77+
VoxelConstants.getLogger().error("Save task failed: {}", description, e);
78+
} catch (Error e) {
79+
VoxelConstants.getLogger().error("Severe save task error: {}", description, e);
80+
}
81+
};
82+
83+
try {
84+
executor.execute(guarded);
85+
} catch (RejectedExecutionException e) {
86+
int skipped = skippedSaveTasks.incrementAndGet();
87+
if (skipped <= 5 || VoxelConstants.DEBUG) {
88+
VoxelConstants.getLogger().debug("Rejected save task during shutdown: {} (skipped count: {})", description, skipped);
89+
}
90+
} catch (RuntimeException e) {
91+
VoxelConstants.getLogger().warn("Failed to submit save task: {}", description, e);
92+
}
93+
}
94+
95+
public static boolean isSaveShutdownInProgress() {
96+
return saveShutdownInProgress;
97+
}
98+
99+
private static ThreadPoolExecutor createSaveExecutor() {
100+
ThreadPoolExecutor executor = new ThreadPoolExecutor(0, concurrentThreads, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
101+
executor.setThreadFactory(new NamedThreadFactory("Voxelmap WorldMap Saver Thread"));
102+
return executor;
41103
}
42104

43105
static {
44106
executorService.setThreadFactory(new NamedThreadFactory("Voxelmap WorldMap Calculation Thread"));
45-
saveExecutorService.setThreadFactory(new NamedThreadFactory("Voxelmap WorldMap Saver Thread"));
46107
}
47108

48109
private static final class NamedThreadFactory implements ThreadFactory {
@@ -54,4 +115,4 @@ private static final class NamedThreadFactory implements ThreadFactory {
54115
@Override
55116
public Thread newThread(@NotNull Runnable r) { return new Thread(r, this.name + " " + this.threadCount.getAndIncrement()); }
56117
}
57-
}
118+
}

0 commit comments

Comments
 (0)