mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-12 16:27:47 +00:00
Fix forced chunks never being ticked by the server (#10325)
This commit is contained in:
1 parent
464483db5b
commit
79efe2ac36
5 files changed
+182
-4
No files matched your search
@@ -24,6 +24,35 @@
|
||||
this.write(chunkpos, completablefuture::join).handle((p_358676_, p_358677_) -> {
|
||||
if (p_358677_ != null) {
|
||||
this.level.getServer().reportChunkSaveFailure(p_358677_, this.storageInfo(), chunkpos);
|
||||
@@ -934,10 +_,27 @@
|
||||
void forEachSpawnCandidateChunk(Consumer<ChunkHolder> p_364582_) {
|
||||
LongIterator longiterator = this.distanceManager.getSpawnCandidateChunks();
|
||||
|
||||
+ // FORGE: separated forEach logic into its own method
|
||||
+ this.forEachChunk(longiterator, this::anyPlayerCloseEnoughForSpawningInternal, p_364582_);
|
||||
+ }
|
||||
+
|
||||
+ /** FORGE: grab forced chunks, use forEachChunk to avoid duplicate logic */
|
||||
+ void forEachForcedChunk(Consumer<ChunkHolder> callback) {
|
||||
+ LongIterator longiterator = this.distanceManager.forcedTickets.keySet().iterator();
|
||||
+ this.forEachChunk(longiterator, pos -> true, callback);
|
||||
+ }
|
||||
+
|
||||
+ /** FORGE: check if there are any forced chunks */
|
||||
+ boolean hasForcedChunks() {
|
||||
+ return !this.distanceManager.forcedTickets.isEmpty();
|
||||
+ }
|
||||
+
|
||||
+ /** FORGE: commonize logic between forEachSpawnCandidateChunk and forEachForcedChunk */
|
||||
+ private void forEachChunk(LongIterator longiterator, java.util.function.Predicate<ChunkPos> filter, Consumer<ChunkHolder> p_364582_) {
|
||||
while (longiterator.hasNext()) {
|
||||
long i = longiterator.nextLong();
|
||||
ChunkHolder chunkholder = this.visibleChunkMap.get(i);
|
||||
- if (chunkholder != null && this.anyPlayerCloseEnoughForSpawningInternal(chunkholder.getPos())) {
|
||||
+ if (chunkholder != null && filter.test(chunkholder.getPos())) {
|
||||
p_364582_.accept(chunkholder);
|
||||
}
|
||||
}
|
||||
@@ -1102,7 +_,7 @@
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
final Executor mainThreadExecutor;
|
||||
private long ticketTickCounter;
|
||||
private int simulationDistance = 10;
|
||||
+ private final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> forcedTickets = new Long2ObjectOpenHashMap<>();
|
||||
+ final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> forcedTickets = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
protected DistanceManager(Executor p_140774_, Executor p_140775_) {
|
||||
TaskScheduler<Runnable> taskscheduler = TaskScheduler.wrapExecutor("player ticket throttler", p_140775_);
|
||||
@@ -44,7 +44,7 @@
|
||||
+ }
|
||||
+
|
||||
+ public <T> void addRegionTicket(TicketType<T> p_140841_, ChunkPos p_140842_, int p_140843_, T p_140844_, boolean forceTicks) {
|
||||
+ Ticket<T> ticket = new Ticket<>(p_140841_, ChunkLevel.byStatus(FullChunkStatus.FULL) - p_140843_, p_140844_, forceTicks);
|
||||
+ Ticket<T> ticket = new Ticket<>(p_140841_, ChunkLevel.byStatus(FullChunkStatus.FULL) - p_140843_, p_140844_, forceTicks);
|
||||
long i = p_140842_.toLong();
|
||||
this.addTicket(i, ticket);
|
||||
this.tickingTicketsTracker.addTicket(i, ticket);
|
||||
|
||||
@@ -11,6 +11,34 @@
|
||||
ChunkAccess chunkaccess1 = chunkholder.getChunkIfPresent(ChunkStatus.FULL);
|
||||
if (chunkaccess1 != null) {
|
||||
this.storeInCache(i, chunkaccess1, ChunkStatus.FULL);
|
||||
@@ -368,9 +_,25 @@
|
||||
}
|
||||
|
||||
private void collectTickingChunks(List<LevelChunk> p_368970_) {
|
||||
- this.chunkMap.forEachSpawnCandidateChunk(p_358696_ -> {
|
||||
+ // FORGE: If there are no forced chunks, just do the vanilla logic
|
||||
+ if (!this.chunkMap.hasForcedChunks()) {
|
||||
+ this.chunkMap.forEachSpawnCandidateChunk(this.getChunkCollector(p_368970_, chunk -> this.level.isNaturalSpawningAllowed(chunk.getPos())));
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ // FORGE: We have forced chunks? Ok, use a linked hash set to preserve order
|
||||
+ Set<LevelChunk> set = new java.util.LinkedHashSet<>();
|
||||
+ this.chunkMap.forEachSpawnCandidateChunk(this.getChunkCollector(set, chunk -> this.level.isNaturalSpawningAllowed(chunk.getPos())));
|
||||
+ this.chunkMap.forEachForcedChunk(this.getChunkCollector(set, chunk -> true));
|
||||
+ p_368970_.addAll(set);
|
||||
+ }
|
||||
+
|
||||
+ /** FORGE: Isolate chunk collection callback */
|
||||
+ private Consumer<ChunkHolder> getChunkCollector(java.util.Collection<LevelChunk> p_368970_, java.util.function.Predicate<LevelChunk> predicate) {
|
||||
+ return (p_358696_ -> {
|
||||
LevelChunk levelchunk = p_358696_.getTickingChunk();
|
||||
- if (levelchunk != null && this.level.isNaturalSpawningAllowed(p_358696_.getPos())) {
|
||||
+ // FORGE: Use custom predicate instead of only checking isNaturalSpawningAllowed
|
||||
+ if (levelchunk != null && predicate.test(levelchunk)) {
|
||||
p_368970_.add(levelchunk);
|
||||
}
|
||||
});
|
||||
@@ -466,11 +_,19 @@
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ import java.util.function.Supplier;
|
||||
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
@@ -46,14 +49,25 @@ public interface IForgeGameTestHelper {
|
||||
}
|
||||
|
||||
default void say(String message, Style style) {
|
||||
var component = ForgeI18n.getPattern(message) != null ? Component.translatable(message) : Component.literal(message);
|
||||
this.say(component.withStyle(style));
|
||||
this.say(this.getMessage(message).withStyle(style));
|
||||
}
|
||||
|
||||
default void say(String message, ChatFormatting style) {
|
||||
this.say(this.getMessage(message).withStyle(style));
|
||||
}
|
||||
|
||||
default void say(String message, int color) {
|
||||
this.say(this.getMessage(message).withColor(color));
|
||||
}
|
||||
|
||||
default void say(Component component) {
|
||||
this.self().getLevel().players().forEach(p -> p.sendSystemMessage(component));
|
||||
}
|
||||
|
||||
private MutableComponent getMessage(String message) {
|
||||
return ForgeI18n.getPattern(message) != null ? Component.translatable(message) : Component.literal(message);
|
||||
}
|
||||
|
||||
default void assertTrue(boolean value, Supplier<String> message) {
|
||||
if (!value)
|
||||
throw new GameTestAssertException(message.get());
|
||||
@@ -180,6 +194,14 @@ public interface IForgeGameTestHelper {
|
||||
return new IntFlag(name);
|
||||
}
|
||||
|
||||
default IntFlag intFlag(String name, int value) {
|
||||
return this.intFlag(name, (long) value);
|
||||
}
|
||||
|
||||
default IntFlag intFlag(String name, long value) {
|
||||
return Util.make(new IntFlag(name), flag -> flag.set(value));
|
||||
}
|
||||
|
||||
default BoolFlag boolFlag(String name) {
|
||||
return new BoolFlag(name);
|
||||
}
|
||||
@@ -258,6 +280,32 @@ public interface IForgeGameTestHelper {
|
||||
super.set(value);
|
||||
}
|
||||
|
||||
public void increment() {
|
||||
this.decrement(1L);
|
||||
}
|
||||
|
||||
public void increment(int amount) {
|
||||
this.decrement((long) amount);
|
||||
}
|
||||
|
||||
public void increment(long amount) {
|
||||
if (this.value != null)
|
||||
this.set(this.value + amount);
|
||||
}
|
||||
|
||||
public void decrement() {
|
||||
this.decrement(1L);
|
||||
}
|
||||
|
||||
public void decrement(int amount) {
|
||||
this.decrement((long) amount);
|
||||
}
|
||||
|
||||
public void decrement(long amount) {
|
||||
if (this.value != null)
|
||||
this.set(this.value - amount);
|
||||
}
|
||||
|
||||
public byte getByte() {
|
||||
return this.value == null ? -1 : this.value.byteValue();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.debug.gameplay.level;
|
||||
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.gametest.framework.GameTest;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.server.level.ChunkLevel;
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.TicketType;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import net.minecraftforge.gametest.GameTestHolder;
|
||||
import net.minecraftforge.test.BaseTestMod;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@GameTestHolder("forge." + ForcedChunkLoadingTest.MOD_ID)
|
||||
@Mod(ForcedChunkLoadingTest.MOD_ID)
|
||||
public class ForcedChunkLoadingTest extends BaseTestMod {
|
||||
static final String MOD_ID = "forced_chunk_loading";
|
||||
|
||||
public ForcedChunkLoadingTest(FMLJavaModLoadingContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
private static final int MAX_CHUNK_LOCATION_ATTEMPTS = 5;
|
||||
|
||||
@GameTest(template = "forge:empty3x3x3")
|
||||
public static void force_far_away_chunk(GameTestHelper helper) {
|
||||
var random = RandomSource.create();
|
||||
var level = helper.getLevel();
|
||||
var chunkSource = level.getChunkSource();
|
||||
|
||||
// try to find a far away chunk that is not already ticking
|
||||
int attempts;
|
||||
var origin = helper.absolutePos(BlockPos.ZERO);
|
||||
ChunkAccess chunk = null;
|
||||
for (attempts = 0; attempts < MAX_CHUNK_LOCATION_ATTEMPTS && chunk == null; attempts++) {
|
||||
int x = random.nextInt(1, 10) * 10000;
|
||||
int z = random.nextInt(1, 10) * 10000;
|
||||
chunk = level.getChunk(origin.offset(x, 0, z));
|
||||
|
||||
// If the chunk is already ticking, we can't force it
|
||||
chunk = chunkSource.isPositionTicking(chunk.getPos().toLong()) ? null : chunk;
|
||||
}
|
||||
|
||||
if (chunk == null) {
|
||||
helper.fail("Failed to find a far away chunk that is not already ticking after " + MAX_CHUNK_LOCATION_ATTEMPTS + " attempts");
|
||||
return;
|
||||
} else if (attempts > 1) {
|
||||
helper.say("WARNING: Finding a far away chunk took " + attempts + " attempts.", ChatFormatting.YELLOW);
|
||||
}
|
||||
|
||||
var pos = chunk.getPos();
|
||||
helper.say("Attempting to force far away chunk: " + pos, ChatFormatting.YELLOW);
|
||||
chunkSource.chunkMap.getDistanceManager().addRegionTicket(TicketType.FORCED, pos, ChunkLevel.byStatus(FullChunkStatus.FULL), pos, true);
|
||||
|
||||
helper.runAfterDelay(20, () -> {
|
||||
helper.assertTrue(chunkSource.chunkMap.getDistanceManager().shouldForceTicks(pos.toLong()), "Chunk is not ticketed as a forced loaded chunk");
|
||||
helper.assertTrue(chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(pos.toLong()), "Forced chunk cannot tick entities");
|
||||
helper.assertTrue(chunkSource.chunkMap.getDistanceManager().inBlockTickingRange(pos.toLong()), "Forced chunk cannot tick blocks");
|
||||
helper.assertTrue(chunkSource.hasChunk(pos.x, pos.z), "Chunk is not loaded despite being ticketed as a force loaded chunk");
|
||||
helper.succeed();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user