Compare commits

...

14 Commits

Author SHA1 Message Date
Spottedleaf
206255e9f5 Set version to 0.2.0-beta.11 2025-06-20 18:45:06 -07:00
Spottedleaf
40a9f1cfcd Do not allow ticket level decreases to be processed asynchronously
Note: This cannot happen on the Fabric/NeoForge versions since
async ticket level processing is not allowed, but can happen on
Paper. This change is made here so that Paper can
remain in sync.

Ticket level decreases may be handled asynchronously when the
off-thread invokes processTicketUpdates() when the main thread
is running ChunkHolderManager#tick(). This is because the ticket
update is queued during tick(), but not executed (invoking
processTicketUpdates) until after releasing the ticket lock.
This creates a small window for an off-thread to invoke
processTicketUpdates() and steal the update.

When the update is stolen, the full chunk status update (if any)
will be eventually queued to execute via the chunk task queue.
If the chunk queue is processed during the server tick at any
point other than the ChunkHolderManager tick, then any ticket
level decrease will violate an important invariant in the
Moonrise chunk system: ticket level decreases only occur during
ChunkHolderManager tick. This invariant exists to make interfacing
with the chunk system easier, especially working with off-thread
contexts.

This change is specifically made to work towards fixing
https://github.com/PaperMC/Folia/issues/363
2025-06-20 18:41:53 -07:00
Spottedleaf
b4789a91f9 Add chunk unload delay config option
The default config option is no unload delay, so that
it maintains parity with Vanilla.

Fixes https://github.com/Tuinity/Moonrise/issues/115
2025-06-20 18:41:00 -07:00
Spottedleaf
97b84f0ccc Update neoforge/fabric, fix incompatibility with "Fast IP Ping"
Inject in a different place to prevent the mixin from failing to
apply. It looks like we do essentially the same logic anyways.
2025-06-20 14:46:00 -07:00
Spottedleaf
1470211fe3 Set version to 0.2.0-SNAPSHOT 2025-06-09 02:23:10 -07:00
Spottedleaf
4d3b4a52a6 Set version to 0.2.0-beta.10 2025-06-09 02:20:27 -07:00
Spottedleaf
a746449be8 Fix infinite loop in RegionFile IO
If an exception is thrown during decompress then the read process
would be started again, which of course would eventually throw in
the decompress process.
2025-06-09 02:19:37 -07:00
Spottedleaf
13948cdf26 Set version to 0.2.0-SNAPSHOT 2025-02-24 21:09:35 -08:00
Spottedleaf
ac0c7deb43 Set version to 0.2.0-beta.9 2025-02-24 21:06:48 -08:00
Jason Penilla
16c8398d8a Clear lastSection on game event listener removal (fixes #87) (#99) 2025-02-24 17:39:07 -07:00
Spottedleaf
661ef813bb Add further information to thread check errors
The entity data is more complete, which will help debug problems
on Folia.
2025-01-28 13:34:32 -08:00
Spottedleaf
cf1d26a73c Set version to 0.2.0-SNAPSHOT 2025-01-27 13:59:26 -08:00
Spottedleaf
0cbff02a1c Set version to 0.2.0-beta.8 2025-01-27 13:57:16 -08:00
Spottedleaf
ce4ee767fe Correctly retrun true for empty input shapes in EntityGetter#isUnobstructed
Vanilla will return true for empty shapes, so we should as well.
2025-01-27 07:51:49 -08:00
13 changed files with 195 additions and 79 deletions

View File

@@ -8,9 +8,9 @@ org.gradle.configuration-cache=true
minecraft_version=1.21.4
loader_version=0.16.9
supported_minecraft_versions=1.21.4
neoforge_version=21.4.33-beta
neoforge_version=21.4.140
neoform_version=1.21.4-20241203.161809
fabric_api_version=0.110.5+1.21.4
fabric_api_version=0.119.3+1.21.4
snakeyaml_version=2.3
concurrentutil_version=0.0.3
yamlconfig_version=1.0.2
@@ -21,6 +21,6 @@ junit_version=5.11.3
fabric_lithium_version=t1FlWYl9
neo_lithium_version=iDqQi66g
# Mod Properties
mod_version=0.2.0-SNAPSHOT
mod_version=0.2.0-beta.11
maven_group=ca.spottedleaf.moonrise
archives_base_name=moonrise

View File

@@ -2,6 +2,7 @@ package ca.spottedleaf.moonrise.common.config.moonrise;
import ca.spottedleaf.moonrise.common.config.ui.ClothConfig;
import ca.spottedleaf.moonrise.common.util.MoonriseCommon;
import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
import ca.spottedleaf.yamlconfig.InitialiseHook;
import ca.spottedleaf.yamlconfig.annotation.Adaptable;
@@ -38,7 +39,7 @@ public final class MoonriseConfig {
@Adaptable
public static final class Basic {
public static final class Basic implements InitialiseHook {
@Serializable(
comment = """
The maximum rate of chunks to send to any given player, per second. If this value is <= 0,
@@ -72,6 +73,20 @@ public final class MoonriseConfig {
section = CHUNK_SYSTEM_SECTION
)
public double playerMaxGenRate = -1.0;
@Serializable(
comment = """
The delay before chunks are unloaded around players once they leave their view distance.
The Vanilla value is 0 ticks. Setting this value higher (i.e 5s) will allow pets to teleport
to their owners when they teleport.
"""
)
public Duration playerChunkUnloadDelay = Duration.parse("0t");
@Override
public void initialise() {
RegionizedPlayerChunkLoader.setUnloadDelay(this.playerChunkUnloadDelay.getTimeTicks());
}
}
@Serializable(

View File

@@ -0,0 +1,44 @@
package ca.spottedleaf.moonrise.common.util;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
public final class EntityUtil {
private static final ThreadLocal<DecimalFormat> THREE_DECIMAL_PLACES = ThreadLocal.withInitial(() -> {
return new DecimalFormat("#,##0.000");
});
private static String formatVec(final Vec3 vec) {
final DecimalFormat format = THREE_DECIMAL_PLACES.get();
return "(" + format.format(vec.x) + "," + format.format(vec.y) + "," + format.format(vec.z) + ")";
}
private static String dumpEntityWithoutReferences(final Entity entity) {
if (entity == null) {
return "{null}";
}
return "{type=" + entity.getClass().getSimpleName() + ",id=" + entity.getId() + ",uuid=" + entity.getUUID() + ",pos=" + formatVec(entity.position())
+ ",mot=" + formatVec(entity.getDeltaMovement()) + ",aabb=" + entity.getBoundingBox() + ",removed=" + entity.getRemovalReason() + ",has_vehicle=" + (entity.getVehicle() != null)
+ ",passenger_count=" + entity.getPassengers().size();
}
public static String dumpEntity(final Entity entity) {
final List<Entity> passengers = entity.getPassengers();
final List<String> passengerStrings = new ArrayList<>(passengers.size());
for (final Entity passenger : passengers) {
passengerStrings.add("(" + dumpEntityWithoutReferences(passenger) + ")");
}
return "{root=[" + dumpEntityWithoutReferences(entity) + "], vehicle=[" + dumpEntityWithoutReferences(entity.getVehicle())
+ "], passengers=[" + String.join(",", passengerStrings) + "]";
}
private EntityUtil() {}
}

View File

@@ -15,21 +15,25 @@ public class TickThread extends Thread {
private static final Logger LOGGER = LoggerFactory.getLogger(TickThread.class);
private static String getThreadContext() {
return "thread=" + Thread.currentThread().getName();
}
/**
* @deprecated
*/
@Deprecated
public static void ensureTickThread(final String reason) {
if (!isTickThread()) {
LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
LOGGER.error("Thread failed main thread check: " + reason + ", context=" + getThreadContext(), new Throwable());
throw new IllegalStateException(reason);
}
}
public static void ensureTickThread(final Level world, final BlockPos pos, final String reason) {
if (!isTickThreadFor(world, pos)) {
final String ex = "Thread " + Thread.currentThread().getName() + " failed main thread check: " +
reason + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos;
final String ex = "Thread failed main thread check: " +
reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos;
LOGGER.error(ex, new Throwable());
throw new IllegalStateException(ex);
}
@@ -37,8 +41,8 @@ public class TickThread extends Thread {
public static void ensureTickThread(final Level world, final BlockPos pos, final int blockRadius, final String reason) {
if (!isTickThreadFor(world, pos, blockRadius)) {
final String ex = "Thread " + Thread.currentThread().getName() + " failed main thread check: " +
reason + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius;
final String ex = "Thread failed main thread check: " +
reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius;
LOGGER.error(ex, new Throwable());
throw new IllegalStateException(ex);
}
@@ -46,8 +50,8 @@ public class TickThread extends Thread {
public static void ensureTickThread(final Level world, final ChunkPos pos, final String reason) {
if (!isTickThreadFor(world, pos)) {
final String ex = "Thread " + Thread.currentThread().getName() + " failed main thread check: " +
reason + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + pos;
final String ex = "Thread failed main thread check: " +
reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + pos;
LOGGER.error(ex, new Throwable());
throw new IllegalStateException(ex);
}
@@ -55,8 +59,8 @@ public class TickThread extends Thread {
public static void ensureTickThread(final Level world, final int chunkX, final int chunkZ, final String reason) {
if (!isTickThreadFor(world, chunkX, chunkZ)) {
final String ex = "Thread " + Thread.currentThread().getName() + " failed main thread check: " +
reason + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ);
final String ex = "Thread failed main thread check: " +
reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ);
LOGGER.error(ex, new Throwable());
throw new IllegalStateException(ex);
}
@@ -64,8 +68,8 @@ public class TickThread extends Thread {
public static void ensureTickThread(final Entity entity, final String reason) {
if (!isTickThreadFor(entity)) {
final String ex = "Thread " + Thread.currentThread().getName() + " failed main thread check: " +
reason + ", entity=" + entity;
final String ex = "Thread failed main thread check: " +
reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity);
LOGGER.error(ex, new Throwable());
throw new IllegalStateException(ex);
}
@@ -73,8 +77,8 @@ public class TickThread extends Thread {
public static void ensureTickThread(final Level world, final AABB aabb, final String reason) {
if (!isTickThreadFor(world, aabb)) {
final String ex = "Thread " + Thread.currentThread().getName() + " failed main thread check: " +
reason + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb;
final String ex = "Thread failed main thread check: " +
reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb;
LOGGER.error(ex, new Throwable());
throw new IllegalStateException(ex);
}
@@ -82,8 +86,8 @@ public class TickThread extends Thread {
public static void ensureTickThread(final Level world, final double blockX, final double blockZ, final String reason) {
if (!isTickThreadFor(world, blockX, blockZ)) {
final String ex = "Thread " + Thread.currentThread().getName() + " failed main thread check: " +
reason + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + new Vec3(blockX, 0.0, blockZ);
final String ex = "Thread failed main thread check: " +
reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + new Vec3(blockX, 0.0, blockZ);
LOGGER.error(ex, new Throwable());
throw new IllegalStateException(ex);
}

View File

@@ -0,0 +1,25 @@
package ca.spottedleaf.moonrise.mixin.chunk_system;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.gameevent.DynamicGameEventListener;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(DynamicGameEventListener.class)
abstract class DynamicGameEventListenerMixin {
@Shadow @Nullable private SectionPos lastSection;
@Inject(method = "remove", at = @At("RETURN"))
private void onRemove(final CallbackInfo ci) {
// We need to unset the last section when removed, otherwise if the same instance is re-added at the same position it
// will assume there was no change and fail to re-register.
// In vanilla, chunks rarely unload and re-load quickly enough to trigger this issue. However, our chunk system has a
// quirk where fast chunk reload cycles will often occur on player login (see PR #22).
// So we fix this vanilla oversight as our changes cause it to manifest in bugs much more often (see issue #87).
this.lastSection = null;
}
}

View File

@@ -74,7 +74,7 @@ interface EntityGetterMixin {
@Overwrite
default boolean isUnobstructed(final Entity entity, final VoxelShape voxel) {
if (voxel.isEmpty()) {
return false;
return true;
}
final AABB singleAABB = ((CollisionVoxelShape)voxel).moonrise$getSingleAABBRepresentation();

View File

@@ -55,7 +55,7 @@ abstract class LevelMixin implements LevelAccessor, AutoCloseable {
public boolean isUnobstructed(final Entity entity) {
final AABB boundingBox = entity.getBoundingBox();
if (CollisionUtil.isEmpty(boundingBox)) {
return false;
return true;
}
final List<Entity> entities = this.getEntities(

View File

@@ -1,10 +1,13 @@
package ca.spottedleaf.moonrise.mixin.serverlist;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.multiplayer.resolver.ServerAddress;
import net.minecraft.client.multiplayer.resolver.ServerAddressResolver;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
@Mixin(ServerAddressResolver.class)
@@ -17,22 +20,21 @@ interface ServerAddressResolverMixin {
@Redirect(
method = {
"method_36903",
"*(Lnet/minecraft/client/multiplayer/resolver/ServerAddress;)Ljava/util/Optional;"
"lambda$static$0"
},
at = @At(
value = "INVOKE",
target = "Ljava/net/InetAddress;getByName(Ljava/lang/String;)Ljava/net/InetAddress;"
value = "NEW",
target = "(Ljava/net/InetAddress;I)Ljava/net/InetSocketAddress;"
)
)
private static InetAddress eliminateRDNS(final String name) throws UnknownHostException {
final InetAddress ret = InetAddress.getByName(name);
final byte[] address = ret.getAddress();
private static InetSocketAddress eliminateRDNS(InetAddress addr, final int port,
@Local(ordinal = 0, argsOnly = true) final ServerAddress serverAddress) throws UnknownHostException {
final byte[] address = addr.getAddress();
if (address != null) {
// pass name to prevent rDNS
return InetAddress.getByAddress(name, address);
addr = InetAddress.getByAddress(serverAddress.getHost(), address);
}
return ret;
return new InetSocketAddress(addr, port);
}
}

View File

@@ -1143,7 +1143,7 @@ public final class MoonriseRegionFileIO {
LOGGER.error("Failed to decompress chunk data for task: " + this.toString(), thr);
}
if (compoundTag == null) {
if (throwable == null && compoundTag == null) {
// need to re-try from the start
this.scheduleReadIO();
return;

View File

@@ -46,7 +46,7 @@ import java.util.function.Function;
public final class RegionizedPlayerChunkLoader {
public static final TicketType<Long> PLAYER_TICKET = TicketType.create("chunk_system:player_ticket", Long::compareTo);
public static final TicketType<Long> PLAYER_TICKET_DELAYED = TicketType.create("chunk_system:player_ticket_delayed", Long::compareTo, 5 * 20);
public static final TicketType<Long> PLAYER_TICKET_DELAYED = TicketType.create("chunk_system:player_ticket_delayed", Long::compareTo, 1);
public static final int MIN_VIEW_DISTANCE = 2;
public static final int MAX_VIEW_DISTANCE = 32;
@@ -55,6 +55,10 @@ public final class RegionizedPlayerChunkLoader {
public static final int LOADED_TICKET_LEVEL = ChunkTaskScheduler.getTicketLevel(ChunkStatus.EMPTY);
public static final int TICK_TICKET_LEVEL = ChunkHolderManager.ENTITY_TICKING_TICKET_LEVEL;
public static void setUnloadDelay(final long ticks) {
PLAYER_TICKET_DELAYED.timeout = Math.max(1, ticks);
}
public static final class ViewDistanceHolder {
private volatile ViewDistances viewDistances;

View File

@@ -1,5 +1,6 @@
package ca.spottedleaf.moonrise.patches.chunk_system.scheduling;
import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock;
import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable;
import ca.spottedleaf.concurrentutil.util.Priority;
@@ -81,6 +82,7 @@ public final class ChunkHolderManager {
private long currentTick;
private final ArrayDeque<NewChunkHolder> pendingFullLoadUpdate = new ArrayDeque<>();
private final MultiThreadedQueue<NewChunkHolder> offThreadPendingFullLoadUpdate = new MultiThreadedQueue<>();
private final ObjectRBTreeSet<NewChunkHolder> autoSaveQueue = new ObjectRBTreeSet<>((final NewChunkHolder c1, final NewChunkHolder c2) -> {
if (c1 == c2) {
return 0;
@@ -109,20 +111,20 @@ public final class ChunkHolderManager {
this.unloadQueue = new ChunkUnloadQueue(((ChunkSystemServerLevel)world).moonrise$getRegionChunkShift());
}
public boolean processTicketUpdates(final int posX, final int posZ) {
public boolean processTicketUpdates(final int chunkX, final int chunkZ) {
final int ticketShift = ThreadedTicketLevelPropagator.SECTION_SHIFT;
final int ticketMask = (1 << ticketShift) - 1;
final List<ChunkProgressionTask> scheduledTasks = new ArrayList<>();
final List<NewChunkHolder> changedFullStatus = new ArrayList<>();
final boolean ret;
final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(
((posX >> ticketShift) - 1) << ticketShift,
((posZ >> ticketShift) - 1) << ticketShift,
(((posX >> ticketShift) + 1) << ticketShift) | ticketMask,
(((posZ >> ticketShift) + 1) << ticketShift) | ticketMask
((chunkX >> ticketShift) - 1) << ticketShift,
((chunkZ >> ticketShift) - 1) << ticketShift,
(((chunkX >> ticketShift) + 1) << ticketShift) | ticketMask,
(((chunkZ >> ticketShift) + 1) << ticketShift) | ticketMask
);
try {
ret = this.processTicketUpdatesNoLock(posX >> ticketShift, posZ >> ticketShift, scheduledTasks, changedFullStatus);
ret = this.processTicketUpdatesNoLock(chunkX >> ticketShift, chunkZ >> ticketShift, scheduledTasks, changedFullStatus);
} finally {
this.ticketLockArea.unlock(ticketLock);
}
@@ -720,6 +722,9 @@ public final class ChunkHolderManager {
return removeDelay <= 0L;
};
final List<ChunkProgressionTask> scheduledTasks = new ArrayList<>();
final List<NewChunkHolder> changedFullStatus = new ArrayList<>();
for (final PrimitiveIterator.OfLong iterator = this.sectionToChunkToExpireCount.keyIterator(); iterator.hasNext();) {
final long sectionKey = iterator.nextLong();
@@ -728,9 +733,16 @@ public final class ChunkHolderManager {
continue;
}
final int lowerChunkX = CoordinateUtils.getChunkX(sectionKey) << sectionShift;
final int lowerChunkZ = CoordinateUtils.getChunkZ(sectionKey) << sectionShift;
final int ticketShift = ThreadedTicketLevelPropagator.SECTION_SHIFT;
final int ticketMask = (1 << ticketShift) - 1;
final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(
CoordinateUtils.getChunkX(sectionKey) << sectionShift,
CoordinateUtils.getChunkZ(sectionKey) << sectionShift
((lowerChunkX >> ticketShift) - 1) << ticketShift,
((lowerChunkZ >> ticketShift) - 1) << ticketShift,
(((lowerChunkX >> ticketShift) + 1) << ticketShift) | ticketMask,
(((lowerChunkZ >> ticketShift) + 1) << ticketShift) | ticketMask
);
try {
@@ -777,9 +789,23 @@ public final class ChunkHolderManager {
if (chunkToExpireCount.isEmpty()) {
this.sectionToChunkToExpireCount.remove(sectionKey);
}
// In order to prevent a race condition where an off-thread invokes processTicketUpdates(), we need to process ticket updates here
// so that we catch any additions to the changed full status list. If an off-thread were to process tickets here, it would not be guaranteed
// that it would be added to the full changed status set by the end of the call - possibly allowing ticket level decreases to be processed
// outside of this call, which is not an intended or expected of this chunk system.
this.processTicketUpdatesNoLock(lowerChunkX >> ThreadedTicketLevelPropagator.SECTION_SHIFT, lowerChunkZ >> ThreadedTicketLevelPropagator.SECTION_SHIFT, scheduledTasks, changedFullStatus);
} finally {
this.ticketLockArea.unlock(ticketLock);
}
this.addChangedStatuses(changedFullStatus);
changedFullStatus.clear(); // clear for next loop iteration
for (int i = 0, len = scheduledTasks.size(); i < len; ++i) {
scheduledTasks.get(i).schedule();
}
scheduledTasks.clear(); // clear for next loop iteration
}
this.processTicketUpdates();
@@ -1006,14 +1032,9 @@ public final class ChunkHolderManager {
return;
}
if (!TickThread.isTickThread()) {
this.taskScheduler.scheduleChunkTask(() -> {
final ArrayDeque<NewChunkHolder> pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate;
for (int i = 0, len = changedFullStatus.size(); i < len; ++i) {
pendingFullLoadUpdate.add(changedFullStatus.get(i));
}
ChunkHolderManager.this.processPendingFullUpdate();
}, Priority.HIGHEST);
// These will be handled on the next ServerChunkCache$MainThreadExecutor#pollTask, as it runs the distance manager update
// which will invoke processTicketUpdates
this.offThreadPendingFullLoadUpdate.addAll(changedFullStatus);
} else {
final ArrayDeque<NewChunkHolder> pendingFullLoadUpdate = this.pendingFullLoadUpdate;
for (int i = 0, len = changedFullStatus.size(); i < len; ++i) {
@@ -1294,36 +1315,20 @@ public final class ChunkHolderManager {
}
public boolean processTicketUpdates() {
return this.processTicketUpdates(true, null);
}
private static final ThreadLocal<List<ChunkProgressionTask>> CURRENT_TICKET_UPDATE_SCHEDULING = new ThreadLocal<>();
static List<ChunkProgressionTask> getCurrentTicketUpdateScheduling() {
return CURRENT_TICKET_UPDATE_SCHEDULING.get();
}
private boolean processTicketUpdates(final boolean processFullUpdates, List<ChunkProgressionTask> scheduledTasks) {
if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) {
throw new IllegalStateException("Cannot update ticket level while unloading chunks or updating entity manager");
}
if (!PlatformHooks.get().allowAsyncTicketUpdates() && !TickThread.isTickThread()) {
final boolean isTickThread = TickThread.isTickThread();
if (!PlatformHooks.get().allowAsyncTicketUpdates() && isTickThread) {
TickThread.ensureTickThread("Cannot asynchronously process ticket updates");
}
List<NewChunkHolder> changedFullStatus = null;
final boolean isTickThread = TickThread.isTickThread();
boolean ret = false;
final boolean canProcessFullUpdates = processFullUpdates & isTickThread;
final boolean canProcessScheduling = scheduledTasks == null;
if (this.ticketLevelPropagator.hasPendingUpdates()) {
if (scheduledTasks == null) {
scheduledTasks = new ArrayList<>();
}
changedFullStatus = new ArrayList<>();
final List<ChunkProgressionTask> scheduledTasks = new ArrayList<>();
final List<NewChunkHolder> changedFullStatus = new ArrayList<>();
this.blockTicketUpdates();
try {
@@ -1334,27 +1339,42 @@ public final class ChunkHolderManager {
} finally {
this.unblockTicketUpdates(Boolean.FALSE);
}
}
if (changedFullStatus != null) {
this.addChangedStatuses(changedFullStatus);
}
if (canProcessScheduling && scheduledTasks != null) {
for (int i = 0, len = scheduledTasks.size(); i < len; ++i) {
scheduledTasks.get(i).schedule();
}
}
if (canProcessFullUpdates) {
if (isTickThread) {
ret |= this.processPendingFullUpdate();
}
return ret;
}
private static final ThreadLocal<List<ChunkProgressionTask>> CURRENT_TICKET_UPDATE_SCHEDULING = new ThreadLocal<>();
static List<ChunkProgressionTask> getCurrentTicketUpdateScheduling() {
return CURRENT_TICKET_UPDATE_SCHEDULING.get();
}
// only call on tick thread
private void processOffThreadFullUpdates() {
final ArrayDeque<NewChunkHolder> pendingFullLoadUpdate = this.pendingFullLoadUpdate;
final MultiThreadedQueue<NewChunkHolder> offThreadPendingFullLoadUpdate = this.offThreadPendingFullLoadUpdate;
NewChunkHolder toUpdate;
while ((toUpdate = offThreadPendingFullLoadUpdate.poll()) != null) {
pendingFullLoadUpdate.add(toUpdate);
}
}
// only call on tick thread
private boolean processPendingFullUpdate() {
this.processOffThreadFullUpdates();
final ArrayDeque<NewChunkHolder> pendingFullLoadUpdate = this.pendingFullLoadUpdate;
boolean ret = false;

View File

@@ -220,6 +220,7 @@ accessible class net/minecraft/server/level/DistanceManager$FixedPlayerDistanceC
# Ticket
accessible field net/minecraft/server/level/Ticket key Ljava/lang/Object;
accessible field net/minecraft/server/level/TicketType timeout J
mutable field net/minecraft/server/level/TicketType timeout J
# ServerChunkCache

View File

@@ -26,6 +26,7 @@
"chunk_system.ChunkStepMixin",
"chunk_system.ChunkStorageMixin",
"chunk_system.DistanceManagerMixin",
"chunk_system.DynamicGameEventListenerMixin",
"chunk_system.EntityGetterMixin",
"chunk_system.EntityMixin",
"chunk_system.EntityTickListMixin",