From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> Date: Sat, 1 Feb 2025 00:33:03 +0300 Subject: [PATCH] Chunk System optimization diff --git a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java index 1b8193587814225c2ef2c5d9e667436eb50ff6c5..b588449cfe766c14a0cf4ea9640b04a51bbcf433 100644 --- a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +++ b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java @@ -59,12 +59,15 @@ public final class NearbyPlayers { public static final int GENERAL_REALLY_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_REALLY_SMALL_VIEW_DISTANCE << 4); private final ServerLevel world; - private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>(); - private final Long2ReferenceOpenHashMap byChunk = new Long2ReferenceOpenHashMap<>(); - private final Long2ReferenceOpenHashMap>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES]; + // DivineMC start - Chunk System optimization + private final Object callbackLock = new Object(); + private final it.unimi.dsi.fastutil.objects.Reference2ReferenceMap players = it.unimi.dsi.fastutil.objects.Reference2ReferenceMaps.synchronize(new Reference2ReferenceOpenHashMap<>()); + private final it.unimi.dsi.fastutil.longs.Long2ReferenceMap byChunk = it.unimi.dsi.fastutil.longs.Long2ReferenceMaps.synchronize(new Long2ReferenceOpenHashMap<>()); + private final it.unimi.dsi.fastutil.longs.Long2ReferenceMap>[] directByChunk = new it.unimi.dsi.fastutil.longs.Long2ReferenceMap[TOTAL_MAP_TYPES]; + // DivineMC end - Chunk System optimization { for (int i = 0; i < this.directByChunk.length; ++i) { - this.directByChunk[i] = new Long2ReferenceOpenHashMap<>(); + this.directByChunk[i] = it.unimi.dsi.fastutil.longs.Long2ReferenceMaps.synchronize(new Long2ReferenceOpenHashMap<>()); // DivineMC - Chunk System optimization } } @@ -188,7 +191,10 @@ public final class NearbyPlayers { final ReferenceList list = this.players[idx]; if (list == null) { ++this.nonEmptyLists; - final ReferenceList players = (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)); + // DivineMC start - Chunk System optimization + this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY); + final ReferenceList players = this.players[idx]; + // DivineMC end - Chunk System optimization this.nearbyPlayers.directByChunk[idx].put(this.chunkKey, players); players.add(player); return; diff --git a/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java index 866f38eb0f379ffbe2888023a7d1c290f521a231..08666b4aa1c7663861dc361f60e6f1cc46694521 100644 --- a/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java +++ b/ca/spottedleaf/moonrise/patches/blockstate_propertyaccess/util/ZeroCollidingReferenceStateTable.java @@ -21,13 +21,15 @@ import net.minecraft.world.level.block.state.properties.Property; public final class ZeroCollidingReferenceStateTable { - private final Int2ObjectOpenHashMap propertyToIndexer; + private final it.unimi.dsi.fastutil.ints.Int2ObjectMap propertyToIndexer; // DivineMC - Chunk System optimization private S[] lookup; private final Collection> properties; public ZeroCollidingReferenceStateTable(final Collection> properties) { - this.propertyToIndexer = new Int2ObjectOpenHashMap<>(properties.size()); - this.properties = new ReferenceArrayList<>(properties); + // DivineMC start - Chunk System optimization + this.propertyToIndexer = it.unimi.dsi.fastutil.ints.Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>(properties.size())); + this.properties = it.unimi.dsi.fastutil.objects.ReferenceLists.synchronize(new ReferenceArrayList<>(properties)); + // DivineMC end - Chunk System optimization final List> sortedProperties = new ArrayList<>(properties); @@ -77,11 +79,11 @@ public final class ZeroCollidingReferenceStateTable { return ret; } - public boolean isLoaded() { + public synchronized boolean isLoaded() { // DivineMC - Chunk System optimization return this.lookup != null; } - public void loadInTable(final Map, Comparable>, S> universe) { + public synchronized void loadInTable(final Map, Comparable>, S> universe) { // DivineMC - Chunk System optimization if (this.lookup != null) { throw new IllegalStateException(); } @@ -117,7 +119,7 @@ public final class ZeroCollidingReferenceStateTable { return ((PropertyAccess)property).moonrise$getById((int)modded); } - public > S set(final long index, final Property property, final T with) { + public synchronized > S set(final long index, final Property property, final T with) { // DivineMC - Chunk System optimization final int newValueId = ((PropertyAccess)property).moonrise$getIdFor(with); if (newValueId < 0) { return null; @@ -139,7 +141,7 @@ public final class ZeroCollidingReferenceStateTable { return this.lookup[(int)newIndex]; } - public > S trySet(final long index, final Property property, final T with, final S dfl) { + public synchronized > S trySet(final long index, final Property property, final T with, final S dfl) { // DivineMC - Chunk System optimization final Indexer indexer = this.propertyToIndexer.get(((PropertyAccess)property).moonrise$getId()); if (indexer == null) { return dfl; diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java index d21ce54ebb5724c04eadf56a2cde701d5eeb5db2..fe0edbb5c91258e620bf42f700aa399bf8b49066 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java @@ -40,9 +40,11 @@ public final class ChunkEntitySlices { private final EntityCollectionBySection allEntities; private final EntityCollectionBySection hardCollidingEntities; - private final Reference2ObjectOpenHashMap, EntityCollectionBySection> entitiesByClass; - private final Reference2ObjectOpenHashMap, EntityCollectionBySection> entitiesByType; - private final EntityList entities = new EntityList(); + // DivineMC start - Chunk System optimization + private final Reference2ObjectMap, EntityCollectionBySection> entitiesByClass; + private final Reference2ObjectMap, EntityCollectionBySection> entitiesByType; + private final java.util.Set entities = com.google.common.collect.Sets.newConcurrentHashSet(); + // DivineMC end - Chunk System optimization public FullChunkStatus status; public final ChunkData chunkData; @@ -67,8 +69,10 @@ public final class ChunkEntitySlices { this.allEntities = new EntityCollectionBySection(this); this.hardCollidingEntities = new EntityCollectionBySection(this); - this.entitiesByClass = new Reference2ObjectOpenHashMap<>(); - this.entitiesByType = new Reference2ObjectOpenHashMap<>(); + // DivineMC start - Chunk System optimization + this.entitiesByClass = it.unimi.dsi.fastutil.objects.Reference2ObjectMaps.synchronize(new Reference2ObjectOpenHashMap<>()); + this.entitiesByType = it.unimi.dsi.fastutil.objects.Reference2ObjectMaps.synchronize(new Reference2ObjectOpenHashMap<>()); + // DivineMC end - Chunk System optimization this.status = status; this.chunkData = chunkData; @@ -134,7 +138,7 @@ public final class ChunkEntitySlices { return null; } - final Entity[] rawData = this.entities.getRawData(); + final Entity[] rawData = this.entities.toArray(new Entity[0]); // DivineMC - Chunk System optimization final List collectedEntities = new ArrayList<>(len); for (int i = 0; i < len; ++i) { final Entity entity = rawData[i]; @@ -153,7 +157,7 @@ public final class ChunkEntitySlices { // returns true if this chunk has transient entities remaining public boolean unload() { final int len = this.entities.size(); - final Entity[] collectedEntities = Arrays.copyOf(this.entities.getRawData(), len); + final Entity[] collectedEntities = Arrays.copyOf(this.entities.toArray(new Entity[0]), len); // DivineMC - Chunk System optimization for (int i = 0; i < len; ++i) { final Entity entity = collectedEntities[i]; @@ -182,7 +186,7 @@ public final class ChunkEntitySlices { return new ArrayList<>(); } - final Entity[] rawData = this.entities.getRawData(); + final Entity[] rawData = this.entities.toArray(new Entity[0]); // DivineMC - Chunk System optimization final List collectedEntities = new ArrayList<>(len); for (int i = 0; i < len; ++i) { collectedEntities.add(rawData[i]); @@ -196,7 +200,7 @@ public final class ChunkEntitySlices { } public void mergeInto(final ChunkEntitySlices slices) { - final Entity[] entities = this.entities.getRawData(); + final Entity[] entities = this.entities.toArray(new Entity[0]); // DivineMC - Chunk System optimization for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { final Entity entity = entities[i]; slices.addEntity(entity, ((ChunkSystemEntity)entity).moonrise$getSectionY()); @@ -221,11 +225,7 @@ public final class ChunkEntitySlices { public void updateStatus(final FullChunkStatus status, final EntityLookup lookup) { this.status = status; - final Entity[] entities = this.entities.getRawData(); - - for (int i = 0, size = this.entities.size(); i < size; ++i) { - final Entity entity = entities[i]; - + for (final Entity entity : this.entities) { // DivineMC - Chunk System optimization final Visibility oldVisibility = EntityLookup.getEntityStatus(entity); ((ChunkSystemEntity)entity).moonrise$setChunkStatus(status); final Visibility newVisibility = EntityLookup.getEntityStatus(entity); @@ -248,10 +248,7 @@ public final class ChunkEntitySlices { this.hardCollidingEntities.addEntity(entity, sectionIndex); } - for (final Iterator, EntityCollectionBySection>> iterator = - this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { - final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); - + for (final java.util.Map.Entry, EntityCollectionBySection> entry : this.entitiesByClass.entrySet()) { // DivineMC - Chunk System optimization if (entry.getKey().isInstance(entity)) { entry.getValue().addEntity(entity, sectionIndex); } @@ -282,10 +279,7 @@ public final class ChunkEntitySlices { this.hardCollidingEntities.removeEntity(entity, sectionIndex); } - for (final Iterator, EntityCollectionBySection>> iterator = - this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { - final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); - + for (final java.util.Map.Entry, EntityCollectionBySection> entry : this.entitiesByClass.entrySet()) { // DivineMC - Chunk System optimization if (entry.getKey().isInstance(entity)) { entry.getValue().removeEntity(entity, sectionIndex); } @@ -436,13 +430,15 @@ public final class ChunkEntitySlices { return false; } - final int size = --this.size; + // DivineMC start - Chunk System optimization + final int lastIdx = --this.size; final E[] storage = this.storage; - if (idx != size) { - System.arraycopy(storage, idx + 1, storage, idx, size - idx); + if (idx < lastIdx) { + storage[idx] = storage[lastIdx]; } - storage[size] = null; + storage[lastIdx] = null; + // DivineMC end - Chunk System optimization return true; } diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java index b28083be4384d6c5efbdce898a0e9d7a2f5bd3d3..76b8d42ae530b59cdaba0583365a557da6b90ede 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java @@ -301,7 +301,7 @@ public final class RegionizedPlayerChunkLoader { return false; } - public void tick() { + public synchronized void tick() { // DivineMC - Chunk System optimization - synchronized TickThread.ensureTickThread("Cannot tick player chunk loader async"); long currTime = System.nanoTime(); for (final ServerPlayer player : new java.util.ArrayList<>(this.world.players())) { @@ -312,6 +312,7 @@ public final class RegionizedPlayerChunkLoader { } loader.update(); // can't invoke plugin logic loader.updateQueues(currTime); + player.connection.resumeFlushing(); // DivineMC - Chunk System optimization } } @@ -362,7 +363,7 @@ public final class RegionizedPlayerChunkLoader { GENERATED_TICKET_LEVEL, TICK_TICKET_LEVEL }; - private final Long2ByteOpenHashMap chunkTicketStage = new Long2ByteOpenHashMap(); + private final it.unimi.dsi.fastutil.longs.Long2ByteMap chunkTicketStage = it.unimi.dsi.fastutil.longs.Long2ByteMaps.synchronize(new Long2ByteOpenHashMap()); // DivineMC - Chunk System optimization { this.chunkTicketStage.defaultReturnValue(CHUNK_TICKET_STAGE_NONE); } @@ -499,7 +500,7 @@ public final class RegionizedPlayerChunkLoader { } @Override - protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { + protected synchronized void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { // DivineMC - Chunk System optimization - synchronized final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); // note: by the time this is called, the tick cleanup should have ran - so, if the chunk is at // the tick stage it was deemed in range for loading. Thus, we need to move it to generated @@ -633,7 +634,7 @@ public final class RegionizedPlayerChunkLoader { return Math.max(Math.abs(dx), Math.abs(dz)) <= this.lastTickDistance; } - private boolean areNeighboursGenerated(final int chunkX, final int chunkZ, final int radius) { + private synchronized boolean areNeighboursGenerated(final int chunkX, final int chunkZ, final int radius) { // DivineMC - Chunk System optimization - synchronized for (int dz = -radius; dz <= radius; ++dz) { for (int dx = -radius; dx <= radius; ++dx) { if ((dx | dz) == 0) { @@ -652,19 +653,11 @@ public final class RegionizedPlayerChunkLoader { return true; } - void updateQueues(final long time) { + synchronized void updateQueues(final long time) { // DivineMC - Chunk System optimization - synchronized TickThread.ensureTickThread(this.player, "Cannot tick player chunk loader async"); if (this.removed) { throw new IllegalStateException("Ticking removed player chunk loader"); } - // update rate limits - final double loadRate = this.getMaxChunkLoadRate(); - final double genRate = this.getMaxChunkGenRate(); - final double sendRate = this.getMaxChunkSendRate(); - - this.chunkLoadTicketLimiter.tickAllocation(time, loadRate, loadRate); - this.chunkGenerateTicketLimiter.tickAllocation(time, genRate, genRate); - this.chunkSendLimiter.tickAllocation(time, sendRate, sendRate); // try to progress chunk loads while (!this.loadingQueue.isEmpty()) { @@ -691,8 +684,7 @@ public final class RegionizedPlayerChunkLoader { } // try to push more chunk loads - final long maxLoads = Math.max(0L, Math.min(MAX_RATE, Math.min(this.loadQueue.size(), this.getMaxChunkLoads()))); - final int maxLoadsThisTick = (int)this.chunkLoadTicketLimiter.takeAllocation(time, loadRate, maxLoads); + final int maxLoadsThisTick = this.loadQueue.size(); // DivineMC - Chunk System optimization if (maxLoadsThisTick > 0) { final LongArrayList chunks = new LongArrayList(maxLoadsThisTick); for (int i = 0; i < maxLoadsThisTick; ++i) { @@ -767,9 +759,7 @@ public final class RegionizedPlayerChunkLoader { } // try to push more chunk generations - final long maxGens = Math.max(0L, Math.min(MAX_RATE, Math.min(this.genQueue.size(), this.getMaxChunkGenerates()))); - // preview the allocations, as we may not actually utilise all of them - final long maxGensThisTick = this.chunkGenerateTicketLimiter.previewAllocation(time, genRate, maxGens); + final long maxGensThisTick = this.genQueue.size(); // DivineMC - Chunk System optimization long ratedGensThisTick = 0L; while (!this.genQueue.isEmpty()) { final long chunkKey = this.genQueue.firstLong(); @@ -799,8 +789,6 @@ public final class RegionizedPlayerChunkLoader { ); this.generatingQueue.enqueue(chunkKey); } - // take the allocations we actually used - this.chunkGenerateTicketLimiter.takeAllocation(time, genRate, ratedGensThisTick); // try to pull ticking chunks while (!this.tickingQueue.isEmpty()) { @@ -830,10 +818,10 @@ public final class RegionizedPlayerChunkLoader { } // try to pull sending chunks - final long maxSends = Math.max(0L, Math.min(MAX_RATE, Integer.MAX_VALUE)); // note: no logic to track concurrent sends - final int maxSendsThisTick = Math.min((int)this.chunkSendLimiter.takeAllocation(time, sendRate, maxSends), this.sendQueue.size()); + final int maxSendsThisTick = this.sendQueue.size(); // DivineMC - Chunk System optimization // we do not return sends that we took from the allocation back because we want to limit the max send rate, not target it for (int i = 0; i < maxSendsThisTick; ++i) { + if (this.sendQueue.isEmpty()) break; // DivineMC - Chunk System optimization final long pendingSend = this.sendQueue.firstLong(); final int pendingSendX = CoordinateUtils.getChunkX(pendingSend); final int pendingSendZ = CoordinateUtils.getChunkZ(pendingSend); @@ -898,9 +886,6 @@ public final class RegionizedPlayerChunkLoader { // reset limiters, they will start at a zero allocation final long time = System.nanoTime(); - this.chunkLoadTicketLimiter.reset(time); - this.chunkGenerateTicketLimiter.reset(time); - this.chunkSendLimiter.reset(time); // now we can update this.update(); @@ -919,10 +904,10 @@ public final class RegionizedPlayerChunkLoader { ); } - void update() { + synchronized void update() { // DivineMC - Chunk System optimization - synchronized TickThread.ensureTickThread(this.player, "Cannot update player asynchronously"); if (this.removed) { - throw new IllegalStateException("Updating removed player chunk loader"); + return; // DivineMC - Chunk System optimization } final ViewDistances playerDistances = ((ChunkSystemServerPlayer)this.player).moonrise$getViewDistanceHolder().getViewDistances(); final ViewDistances worldDistances = ((ChunkSystemServerLevel)this.world).moonrise$getViewDistanceHolder().getViewDistances(); @@ -1071,7 +1056,7 @@ public final class RegionizedPlayerChunkLoader { this.flushDelayedTicketOps(); } - void remove() { + synchronized void remove() { // DivineMC - Chunk System optimization - synchronized TickThread.ensureTickThread(this.player, "Cannot add player asynchronously"); if (this.removed) { throw new IllegalStateException("Removing removed player chunk loader"); @@ -1099,7 +1084,7 @@ public final class RegionizedPlayerChunkLoader { } public LongOpenHashSet getSentChunksRaw() { - return this.sentChunks; + return new LongOpenHashSet(this.sentChunks); // DivineMC - Chunk System optimization } } } diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java index 0c99bffa769d53562a10d23c4a9b37dc59c7f478..6094b9f2d4a686a4c639c739d182aba7aac430e8 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java @@ -1208,6 +1208,27 @@ public final class ChunkHolderManager { } } + // DivineMC start - Chunk System optimization + public final org.agrona.collections.Object2ObjectHashMap blockTickingChunkHolders = new org.agrona.collections.Object2ObjectHashMap<>(16384, 0.25f); + public final org.agrona.collections.Object2ObjectHashMap entityTickingChunkHolders = new org.agrona.collections.Object2ObjectHashMap<>(16384, 0.25f); + + public void markBlockTicking(NewChunkHolder newChunkHolder) { + this.blockTickingChunkHolders.put(newChunkHolder.getCachedLongPos(), newChunkHolder); + } + + public void markNonBlockTickingIfPossible(NewChunkHolder newChunkHolder) { + this.blockTickingChunkHolders.remove(newChunkHolder.getCachedLongPos()); + } + + public void markEntityTicking(NewChunkHolder newChunkHolder) { + this.entityTickingChunkHolders.put(newChunkHolder.getCachedLongPos(), newChunkHolder); + } + + public void markNonEntityTickingIfPossible(NewChunkHolder newChunkHolder) { + this.entityTickingChunkHolders.remove(newChunkHolder.getCachedLongPos()); + } + // DivineMC end - Chunk System optimization + public enum TicketOperationType { ADD, REMOVE, ADD_IF_REMOVED, ADD_AND_REMOVE } diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java index e4a5fa25ed368fc4662c30934da2963ef446d782..6da0ea5cd83a00578223e0a19f952c917bcbcdae 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java @@ -644,11 +644,19 @@ public final class NewChunkHolder { } public final ChunkHolder vanillaChunkHolder; + // DivineMC start - Chunk System optimization + private final long cachedLongPos; + + public long getCachedLongPos() { + return cachedLongPos; + } + // DivineMC end - Chunk System optimization public NewChunkHolder(final ServerLevel world, final int chunkX, final int chunkZ, final ChunkTaskScheduler scheduler) { this.world = world; this.chunkX = chunkX; this.chunkZ = chunkZ; + this.cachedLongPos = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ); // DivineMC - Chunk System optimization this.scheduler = scheduler; this.vanillaChunkHolder = new ChunkHolder( new ChunkPos(chunkX, chunkZ), ChunkHolderManager.MAX_TICKET_LEVEL, world, @@ -790,9 +798,11 @@ public final class NewChunkHolder { // note: these are completed with null to indicate that no write occurred // they are also completed with null to indicate a null write occurred - private UnloadTask chunkDataUnload; - private UnloadTask entityDataUnload; - private UnloadTask poiDataUnload; + // DivineMC start - Chunk System optimization + private volatile UnloadTask chunkDataUnload; + private volatile UnloadTask entityDataUnload; + private volatile UnloadTask poiDataUnload; + // DivineMC end - Chunk System optimization public static final record UnloadTask(CallbackCompletable completable, PrioritisedExecutor.PrioritisedTask task, LazyRunnable toRun) {} @@ -1190,6 +1200,7 @@ public final class NewChunkHolder { for (int dz = -NEIGHBOUR_RADIUS; dz <= NEIGHBOUR_RADIUS; ++dz) { for (int dx = -NEIGHBOUR_RADIUS; dx <= NEIGHBOUR_RADIUS; ++dx) { final NewChunkHolder holder = (dx | dz) == 0 ? this : this.scheduler.chunkHolderManager.getChunkHolder(dx + this.chunkX, dz + this.chunkZ); + if (holder == null) continue; // DivineMC - Chunk System optimization if (loaded) { if (holder.setNeighbourFullLoaded(-dx, -dz)) { changedFullStatus.add(holder); @@ -1214,6 +1225,19 @@ public final class NewChunkHolder { private void updateCurrentState(final FullChunkStatus to) { this.currentFullChunkStatus = to; + // DivineMC start - Chunk System optimization + if (to.isOrAfter(FullChunkStatus.BLOCK_TICKING)) { + this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markBlockTicking(this); + } else { + this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markNonBlockTickingIfPossible(this); + } + + if (to.isOrAfter(FullChunkStatus.ENTITY_TICKING)) { + this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markEntityTicking(this); + } else { + this.world.moonrise$getChunkTaskScheduler().chunkHolderManager.markNonEntityTickingIfPossible(this); + } + // DivineMC end - Chunk System optimization } // only to be called on the main thread, no locks need to be held @@ -1348,11 +1372,11 @@ public final class NewChunkHolder { return this.requestedGenStatus; } - private final Reference2ObjectOpenHashMap>> statusWaiters = new Reference2ObjectOpenHashMap<>(); + private final Map>> statusWaiters = new java.util.concurrent.ConcurrentHashMap<>(); // DivineMC - Chunk System optimization void addStatusConsumer(final ChunkStatus status, final Consumer consumer) { this.statusWaiters.computeIfAbsent(status, (final ChunkStatus keyInMap) -> { - return new ArrayList<>(4); + return new java.util.concurrent.CopyOnWriteArrayList<>(); // DivineMC - Chunk System optimization }).add(consumer); } @@ -1394,11 +1418,11 @@ public final class NewChunkHolder { }, Priority.HIGHEST); } - private final Reference2ObjectOpenHashMap>> fullStatusWaiters = new Reference2ObjectOpenHashMap<>(); + private final Map>> fullStatusWaiters = new java.util.concurrent.ConcurrentHashMap<>(); void addFullStatusConsumer(final FullChunkStatus status, final Consumer consumer) { this.fullStatusWaiters.computeIfAbsent(status, (final FullChunkStatus keyInMap) -> { - return new ArrayList<>(4); + return new java.util.concurrent.CopyOnWriteArrayList<>(); // DivineMC - Chunk System optimization }).add(consumer); } diff --git a/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java b/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java index e97e7d276faf055c89207385d3820debffb06463..4aeb75a2cdcfb4206bab3eee5ad674dd9890e720 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java +++ b/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java @@ -2,6 +2,6 @@ package ca.spottedleaf.moonrise.patches.chunk_tick_iteration; public final class ChunkTickConstants { - public static final int PLAYER_SPAWN_TRACK_RANGE = 8; + public static final int PLAYER_SPAWN_TRACK_RANGE = (int) Math.round(org.bxteam.divinemc.DivineConfig.playerNearChunkDetectionRange / 16.0); // DivineMC - Chunk System optimization } diff --git a/net/minecraft/server/level/DistanceManager.java b/net/minecraft/server/level/DistanceManager.java index 5eab6179ce3913cb4e4d424f910ba423faf21c85..189205fbeed7673398fa6f7706864d3723467811 100644 --- a/net/minecraft/server/level/DistanceManager.java +++ b/net/minecraft/server/level/DistanceManager.java @@ -178,14 +178,14 @@ public abstract class DistanceManager implements ca.spottedleaf.moonrise.patches public boolean inEntityTickingRange(long chunkPos) { // Paper start - rewrite chunk system - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkHolderManager().getChunkHolder(chunkPos); + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkHolderManager().entityTickingChunkHolders.get(chunkPos); // DivineMC - Chunk System optimization return chunkHolder != null && chunkHolder.isEntityTickingReady(); // Paper end - rewrite chunk system } public boolean inBlockTickingRange(long chunkPos) { // Paper start - rewrite chunk system - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkHolderManager().getChunkHolder(chunkPos); + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkHolderManager().blockTickingChunkHolders.get(chunkPos); // DivineMC - Chunk System optimization return chunkHolder != null && chunkHolder.isTickingReady(); // Paper end - rewrite chunk system } diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java index 2678bf59d557f085c7265e2f3eb038647723d35e..b30d6968fba2ab4d9cde0ac9d4f1cfc629c65359 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -441,7 +441,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon public boolean isPositionTicking(long chunkPos) { // Paper start - rewrite chunk system - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder newChunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos); + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder newChunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.blockTickingChunkHolders.get(chunkPos); // DivineMC Chunk System optimization return newChunkHolder != null && newChunkHolder.isTickingReady(); // Paper end - rewrite chunk system } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index a3f363d0c86142e03edc7fc6e2ff6ed81de8ed65..57668810e86b1f293c661d01c2486a3da7256c1e 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -857,7 +857,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @Override public boolean shouldTickBlocksAt(long chunkPos) { // Paper start - rewrite chunk system - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder holder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos); + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder holder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.blockTickingChunkHolders.get(chunkPos); // DivineMC - Chunk System optimization return holder != null && holder.isTickingReady(); // Paper end - rewrite chunk system } @@ -2567,7 +2567,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { // Paper start - rewrite chunk system - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos); + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.blockTickingChunkHolders.get(chunkPos); // DivineMC - Chunk System optimization // isTicking implies the chunk is loaded, and the chunk is loaded now implies the entities are loaded return chunkHolder != null && chunkHolder.isTickingReady(); // Paper end - rewrite chunk system @@ -2582,14 +2582,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe public boolean isNaturalSpawningAllowed(BlockPos pos) { // Paper start - rewrite chunk system - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos)); + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.entityTickingChunkHolders.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos)); // DivineMC - Chunk System optimization return chunkHolder != null && chunkHolder.isEntityTickingReady(); // Paper end - rewrite chunk system } public boolean isNaturalSpawningAllowed(ChunkPos chunkPos) { // Paper start - rewrite chunk system - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkPos)); + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.entityTickingChunkHolders.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkPos)); // DivineMC - Chunk System optimization return chunkHolder != null && chunkHolder.isEntityTickingReady(); // Paper end - rewrite chunk system } diff --git a/net/minecraft/world/level/LevelReader.java b/net/minecraft/world/level/LevelReader.java index 26c8c1e5598daf3550aef05b12218c47bda6618b..94c824ab1457939c425e1f99929d3222ee2c18a0 100644 --- a/net/minecraft/world/level/LevelReader.java +++ b/net/minecraft/world/level/LevelReader.java @@ -70,10 +70,27 @@ public interface LevelReader extends ca.spottedleaf.moonrise.patches.chunk_syste @Override default Holder getNoiseBiome(int x, int y, int z) { - ChunkAccess chunk = this.getChunk(QuartPos.toSection(x), QuartPos.toSection(z), ChunkStatus.BIOMES, false); + ChunkAccess chunk = this.fasterChunkAccess(this, QuartPos.toSection(x), QuartPos.toSection(z), ChunkStatus.BIOMES, false); // DivineMC - Chunk System optimization return chunk != null ? chunk.getNoiseBiome(x, y, z) : this.getUncachedNoiseBiome(x, y, z); } + // DivineMC start - Chunk System optimization + private @Nullable ChunkAccess fasterChunkAccess(LevelReader instance, int x, int z, ChunkStatus chunkStatus, boolean create) { + if (!create && instance instanceof net.minecraft.server.level.ServerLevel world) { + final net.minecraft.server.level.ChunkHolder holder = (world.getChunkSource().chunkMap).getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); + if (holder != null) { + final java.util.concurrent.CompletableFuture> future = holder.getFullChunkFuture(); + final net.minecraft.server.level.ChunkResult either = future.getNow(null); + if (either != null) { + final net.minecraft.world.level.chunk.LevelChunk chunk = either.orElse(null); + if (chunk != null) return chunk; + } + } + } + return instance.getChunk(x, z, chunkStatus, create); + } + // DivineMC end - Chunk System optimization + Holder getUncachedNoiseBiome(int x, int y, int z); boolean isClientSide(); diff --git a/net/minecraft/world/level/chunk/storage/IOWorker.java b/net/minecraft/world/level/chunk/storage/IOWorker.java index 2199a9e2a0141c646d108f2687a27f1d165453c5..c28c2583b257f92207b822a1fdde8f5b7e480992 100644 --- a/net/minecraft/world/level/chunk/storage/IOWorker.java +++ b/net/minecraft/world/level/chunk/storage/IOWorker.java @@ -212,7 +212,38 @@ public class IOWorker implements ChunkScanAccess, AutoCloseable { }); } + // DivineMC start - Chunk System optimization + private void checkHardLimit() { + if (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheLimit) { + LOGGER.warn("Chunk data cache size exceeded hard limit ({} >= {}), forcing writes to disk (you can increase chunkDataCacheLimit in c2me.toml)", this.pendingWrites.size(), org.bxteam.divinemc.DivineConfig.chunkDataCacheLimit); + while (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit * 0.75) { + writeResult0(); + } + } + } + + private void writeResult0() { + java.util.Iterator> iterator = this.pendingWrites.entrySet().iterator(); + if (iterator.hasNext()) { + java.util.Map.Entry entry = iterator.next(); + iterator.remove(); + this.runStore(entry.getKey(), entry.getValue()); + } + } + // DivineMC end - Chunk System optimization + private void storePendingChunk() { + // DivineMC start - Chunk System optimization + if (!this.pendingWrites.isEmpty()) { + checkHardLimit(); + if (this.pendingWrites.size() >= org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit) { + int writeFrequency = Math.min(1, (this.pendingWrites.size() - (int) org.bxteam.divinemc.DivineConfig.chunkDataCacheSoftLimit) / 16); + for (int i = 0; i < writeFrequency; i++) { + writeResult0(); + } + } + } + // DivineMC end - Chunk System optimization Entry entry = this.pendingWrites.pollFirstEntry(); if (entry != null) { this.runStore(entry.getKey(), entry.getValue()); diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java index 6ebd1300c2561116b83cb2472ac7939ead36d576..16cd10ab8de69ca3d29c84cf93715645322fd72a 100644 --- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java @@ -244,7 +244,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise protected RegionFileStorage(RegionStorageInfo info, Path folder, boolean sync) { // Paper - protected this.folder = folder; - this.sync = sync; + this.sync = Boolean.parseBoolean(System.getProperty("com.ishland.c2me.chunkio.syncDiskWrites", String.valueOf(sync))); // DivineMC - C2ME: sync disk writes this.info = info; this.isChunkData = isChunkDataFolder(this.folder); // Paper - recalculate region file headers }