From 34d5e51e97d00f4f800b57c97001f09a91c4fa78 Mon Sep 17 00:00:00 2001 From: QUENTIN453 Date: Sat, 6 May 2023 00:46:53 +0200 Subject: [PATCH] optimizations --- dependencies.gradle | 2 + .../fr/iamacat/multithreading/asm/Mixin.java | 1 - ...hreadingandtweaksMultithreadingConfig.java | 2 +- .../client/core/MixinEntitiesRendering.java | 69 +++++------ .../client/core/MixinLiquidRendering.java | 48 +++++--- .../mixins/client/core/MixinTileEntities.java | 111 +++++++++--------- .../mixins/client/core/MixinWorldgen.java | 54 +++++---- .../common/core/MixinChunkPopulating.java | 28 +++-- .../common/core/MixinGrowthSpreading.java | 12 +- .../mixins/common/core/MixinWorldTick.java | 86 +++++++++++--- 10 files changed, 246 insertions(+), 167 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index 108c1145..574b5784 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -46,5 +46,7 @@ dependencies { implementation("org.joml:joml:1.10.5") implementation 'org.apache.logging.log4j:log4j-core:2.16.0' + + compile group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1' } diff --git a/src/main/java/fr/iamacat/multithreading/asm/Mixin.java b/src/main/java/fr/iamacat/multithreading/asm/Mixin.java index 89bc0ff1..d3c26d0b 100644 --- a/src/main/java/fr/iamacat/multithreading/asm/Mixin.java +++ b/src/main/java/fr/iamacat/multithreading/asm/Mixin.java @@ -70,7 +70,6 @@ public enum Mixin implements IMixin { client_core_MixinLiquidRendering(Side.CLIENT, m -> MultithreadingandtweaksMultithreadingConfig.enableMixinLiquidRendering, "core.MixinLiquidRendering"), - // MOD-FILTERED MIXINS // The modFilter argument is a predicate, so you can also use the .and(), .or(), and .negate() methods to mix and diff --git a/src/main/java/fr/iamacat/multithreading/config/MultithreadingandtweaksMultithreadingConfig.java b/src/main/java/fr/iamacat/multithreading/config/MultithreadingandtweaksMultithreadingConfig.java index ec0e8a7d..6f83163a 100644 --- a/src/main/java/fr/iamacat/multithreading/config/MultithreadingandtweaksMultithreadingConfig.java +++ b/src/main/java/fr/iamacat/multithreading/config/MultithreadingandtweaksMultithreadingConfig.java @@ -60,7 +60,7 @@ public class MultithreadingandtweaksMultithreadingConfig { @Config.RequiresMcRestart public static boolean enableMixinTileEntities; - @Config.Comment("Enable multithreaded Worldgen") + @Config.Comment("Enable multithreaded Worldgen)") @Config.DefaultBoolean(true) @Config.RequiresMcRestart public static boolean enableMixinWorldgen; diff --git a/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinEntitiesRendering.java b/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinEntitiesRendering.java index 2e75e38e..7ca68d2a 100644 --- a/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinEntitiesRendering.java +++ b/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinEntitiesRendering.java @@ -5,7 +5,6 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.*; -import java.util.stream.Collectors; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.entity.Entity; @@ -70,47 +69,39 @@ public void onDoRender(Entity entity, double x, double y, double z, float yaw, f public void renderEntities(World world, Collection entities, RenderManager renderManager, float tickDelta) { if (MultithreadingandtweaksMultithreadingConfig.enableMixinEntitiesRendering) { - int batchSize = BATCH_SIZE; // The maximum number of entities to render per batch - List> entityBatches = splitEntitiesIntoBatches(entities); - - // Submit rendering tasks to the thread pool using parallel stream - List> renderingFutures = entityBatches.parallelStream() - .map(entityBatch -> CompletableFuture.runAsync(() -> { - for (Entity entity : entityBatch) { - renderManager.renderEntityWithPosYaw( - entity, - entity.posX - RenderManager.renderPosX, - entity.posY - RenderManager.renderPosY, - entity.posZ - RenderManager.renderPosZ, - entity.rotationYaw, - tickDelta); - } - }, executorService)) - .collect(Collectors.toList()); - - // Wait for all rendering tasks to complete - CompletableFuture.allOf(renderingFutures.toArray(new CompletableFuture[0])) - .join(); - } - } + List entityList = new ArrayList<>(entities); + int entityCount = entityList.size(); + if (entityCount == 0) { + return; + } - private List> splitEntitiesIntoBatches(Collection entities) { - if (MultithreadingandtweaksMultithreadingConfig.enableMixinFireTick) { - int batchSize = BATCH_SIZE; // The maximum number of entities to render per batch + // Compute the number of tasks to use based on the number of available processors + int taskCount = Math.min(entityCount, ForkJoinPool.getCommonPoolParallelism()); + int batchSize = (int) Math.ceil((double) entityCount / taskCount); + + // Split entities into batches List> entityBatches = new ArrayList<>(); - List entityBatch = new ArrayList<>(); - for (Entity entity : entities) { - entityBatch.add(entity); - if (entityBatch.size() == batchSize) { - entityBatches.add(entityBatch); - entityBatch = new ArrayList<>(); - } + for (int i = 0; i < entityCount; i += batchSize) { + int end = Math.min(i + batchSize, entityCount); + entityBatches.add(entityList.subList(i, end)); } - if (!entityBatch.isEmpty()) { - entityBatches.add(entityBatch); - } - return entityBatches; + + // Submit rendering tasks to the ForkJoinPool + ForkJoinPool.commonPool() + .execute(() -> { + entityBatches.parallelStream() + .forEach(entityBatch -> { + for (Entity entity : entityBatch) { + renderManager.renderEntityWithPosYaw( + entity, + entity.posX - RenderManager.renderPosX, + entity.posY - RenderManager.renderPosY, + entity.posZ - RenderManager.renderPosZ, + entity.rotationYaw, + tickDelta); + } + }); + }); } - return new ArrayList<>(); } } diff --git a/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinLiquidRendering.java b/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinLiquidRendering.java index b3343d07..589b1aa2 100644 --- a/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinLiquidRendering.java +++ b/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinLiquidRendering.java @@ -5,12 +5,10 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import cpw.mods.fml.client.FMLClientHandler; import net.minecraft.block.Block; import net.minecraft.block.BlockLiquid; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.Tessellator; -import net.minecraft.client.renderer.WorldRenderer; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureMap; import net.minecraft.util.EnumFacing; @@ -24,6 +22,7 @@ import com.falsepattern.lib.compat.BlockPos; +import cpw.mods.fml.client.FMLClientHandler; import fr.iamacat.multithreading.config.MultithreadingandtweaksMultithreadingConfig; @Mixin(BlockLiquid.class) @@ -58,16 +57,24 @@ private void tesselateFluid(IBlockAccess world, BlockPos pos, Tessellator tessel } private void tesselateBatch(World world, List positions, Tessellator tessellator, - Set visitedBlocks, int state) { - Block block = world.getBlock(positions.get(0).getX(), positions.get(0).getY(), positions.get(0).getZ()); + Set visitedBlocks, int state) { + Block block = world.getBlock( + positions.get(0) + .getX(), + positions.get(0) + .getY(), + positions.get(0) + .getZ()); List fluidBlocks = new ArrayList<>(); - Minecraft minecraft = FMLClientHandler.instance().getClient(); + Minecraft minecraft = FMLClientHandler.instance() + .getClient(); TextureMap textureMapBlocks = minecraft.getTextureMapBlocks(); for (BlockPos blockPos : positions) { Block currentBlock = world.getBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ()); - if (currentBlock.getMaterial().isLiquid() && currentBlock == block) { + if (currentBlock.getMaterial() + .isLiquid() && currentBlock == block) { for (EnumFacing direction : EnumFacing.values()) { BlockPos offset = blockPos.offset(direction); if (!visitedBlocks.add(offset)) { @@ -76,13 +83,24 @@ private void tesselateBatch(World world, List positions, Tessellator t Block offsetBlock = world.getBlock(offset.getX(), offset.getY(), offset.getZ()); int offsetMetadata = world.getBlockMetadata(offset.getX(), offset.getY(), offset.getZ()); - if (offsetBlock.getMaterial().isReplaceable() || - (offsetBlock.getMaterial().isLiquid() && offsetBlock == block && offsetMetadata == state)) { - putFluidVertex(tessellator, blockPos.getX(), blockPos.getY(), blockPos.getZ(), - textureMapBlocks.getTextureExtry(offsetBlock.getIcon(0, offsetMetadata).getIconName()), direction); - } else if (offsetBlock.getMaterial().isLiquid() && offsetBlock == block) { - fluidBlocks.add(offset); - } + if (offsetBlock.getMaterial() + .isReplaceable() + || (offsetBlock.getMaterial() + .isLiquid() && offsetBlock == block + && offsetMetadata == state)) { + putFluidVertex( + tessellator, + blockPos.getX(), + blockPos.getY(), + blockPos.getZ(), + textureMapBlocks.getTextureExtry( + offsetBlock.getIcon(0, offsetMetadata) + .getIconName()), + direction); + } else if (offsetBlock.getMaterial() + .isLiquid() && offsetBlock == block) { + fluidBlocks.add(offset); + } } } } @@ -92,8 +110,8 @@ private void tesselateBatch(World world, List positions, Tessellator t } } - - private void putFluidVertex(Tessellator tessellator, double x, double y, double z, TextureAtlasSprite sprite, EnumFacing facing) { + private void putFluidVertex(Tessellator tessellator, double x, double y, double z, TextureAtlasSprite sprite, + EnumFacing facing) { float minU, maxU, minV, maxV; if (facing == EnumFacing.UP || facing == EnumFacing.DOWN) { minU = sprite.getInterpolatedU(x * 8); diff --git a/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinTileEntities.java b/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinTileEntities.java index 5fb496d8..5a0ae1d2 100644 --- a/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinTileEntities.java +++ b/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinTileEntities.java @@ -1,12 +1,12 @@ package fr.iamacat.multithreading.mixins.client.core; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.*; import java.util.stream.Collectors; import java.util.stream.IntStream; +import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher; import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer; import net.minecraft.nbt.NBTTagCompound; @@ -25,10 +25,11 @@ public abstract class MixinTileEntities { private static final int BATCH_SIZE = MultithreadingandtweaksMultithreadingConfig.batchsize; + private static final ExecutorService THREAD_POOL = Executors + .newFixedThreadPool(MultithreadingandtweaksMultithreadingConfig.numberofcpus); private World world; private List tileEntities; - private final Object lock = new Object(); @Inject(method = "init", at = @At("RETURN")) public void init(CallbackInfo ci) { @@ -36,37 +37,41 @@ public void init(CallbackInfo ci) { this.tileEntities = new ArrayList<>(); } - @Inject(method = "func_147455_a", at = @At("RETURN")) public void addTileEntity(TileEntity tileEntity) { - synchronized (lock) { - this.tileEntities.add(tileEntity); - } + this.tileEntities.add(tileEntity); } - @Inject(method = "process", at = @At("RETURN")) - public void process(CallbackInfo ci) { + public void processTileEntities() { // Split the tile entities into batches List> batches = splitIntoBatches(this.tileEntities); - // Process each batch using a for loop + // Process each batch using a thread pool + List> futures = new ArrayList<>(); for (List batch : batches) { - processBatch(batch); + futures.add(THREAD_POOL.submit(() -> processBatch(batch))); + } + + // Wait for all tasks to complete + for (Future future : futures) { + try { + future.get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } } } private void processBatch(List batch) { - if (MultithreadingandtweaksMultithreadingConfig.enableMixinTileEntities) { - for (TileEntity tileEntity : batch) { - // Process each tile entity - tileEntity.updateEntity(); - NBTTagCompound tag = new NBTTagCompound(); - tileEntity.writeToNBT(tag); - this.world.setTileEntity( - tileEntity.xCoord, - tileEntity.yCoord, - tileEntity.zCoord, - TileEntity.createAndLoadEntity(tag)); - } + for (TileEntity tileEntity : batch) { + // Process each tile entity + tileEntity.updateEntity(); + NBTTagCompound tag = new NBTTagCompound(); + tileEntity.writeToNBT(tag); + this.world.setTileEntity( + tileEntity.xCoord, + tileEntity.yCoord, + tileEntity.zCoord, + TileEntity.createAndLoadEntity(tag)); } } @@ -79,42 +84,34 @@ private static List> splitIntoBatches(List list) { @Inject(method = "renderTileEntities", at = @At("HEAD")) public void renderTileEntities(World world, List tileEntities, CallbackInfo ci) { - // Use a ForkJoinPool with the number of threads equal to the number of available processors - ForkJoinPool pool = new ForkJoinPool(MultithreadingandtweaksMultithreadingConfig.numberofcpus); - - // Process each tile entity in parallel - pool.submit( - () -> tileEntities.parallelStream() - .forEach(tileEntity -> { - if (tileEntity != null) { - world.addTileEntity(tileEntity); - } - })) - .join(); - - pool.shutdown(); + // Process each tile entity in parallel using a thread pool + List> futures = new ArrayList<>(); + for (TileEntity tileEntity : tileEntities) { + if (tileEntity != null) { + futures.add(THREAD_POOL.submit(() -> renderTileEntity(tileEntity))); + } + } + + // Wait for all tasks to complete + for (Future future : futures) { + try { + future.get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + } } - public void render(TileEntity tileentity, float partialTicks, int destroyStage, double x, double y, double z) { - double dx = tileentity.xCoord - x; - double dy = tileentity.yCoord - y; - double dz = tileentity.zCoord - z; - double distanceSq = dx * dx + dy * dy + dz * dz; - if (distanceSq < 4096.0D) { - // Use multithreaded rendering - List tileEntities = Collections.singletonList(tileentity); - tileEntities.parallelStream() - .forEach(te -> { - GL11.glPushMatrix(); - GL11.glTranslated(te.xCoord + 0.5 - x, te.yCoord + 0.5 - y, te.zCoord + 0.5 - z); - TileEntitySpecialRenderer renderer = TileEntityRendererDispatcher.instance.getSpecialRenderer(te); - if (renderer != null) { - if (te.shouldRenderInPass(destroyStage)) { - renderer.renderTileEntityAt(te, x, y, z, partialTicks); - } - } - GL11.glPopMatrix(); - }); + private void renderTileEntity(TileEntity tileentity) { + GL11.glPushMatrix(); + GL11.glTranslated( + tileentity.xCoord + 0.5 - RenderManager.renderPosX, + tileentity.yCoord + 0.5 - RenderManager.renderPosY, + tileentity.zCoord + 0.5 - RenderManager.renderPosZ); + TileEntitySpecialRenderer renderer = TileEntityRendererDispatcher.instance.getSpecialRenderer(tileentity); + if (renderer != null) { + renderer.renderTileEntityAt(tileentity, 0, 0, 0, 0); } + GL11.glPopMatrix(); } } diff --git a/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinWorldgen.java b/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinWorldgen.java index b3d2cf7e..1c1d4c27 100644 --- a/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinWorldgen.java +++ b/src/main/java/fr/iamacat/multithreading/mixins/client/core/MixinWorldgen.java @@ -4,6 +4,7 @@ import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityPlayerSP; @@ -18,24 +19,16 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import com.falsepattern.lib.compat.ChunkPos; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import fr.iamacat.multithreading.config.MultithreadingandtweaksMultithreadingConfig; @Mixin(WorldClient.class) public abstract class MixinWorldgen { + private WorldClient world; - private final ThreadPoolExecutor executorService = new ThreadPoolExecutor( - MultithreadingandtweaksMultithreadingConfig.numberofcpus, - MultithreadingandtweaksMultithreadingConfig.numberofcpus, - 60L, - TimeUnit.SECONDS, - new LinkedBlockingQueue<>(), - new ThreadFactoryBuilder().setNameFormat("Worldgen-%d") - .build()); - - private World world; + private final ExecutorService executorService = Executors.newFixedThreadPool(6); private final Map loadedChunks = new ConcurrentHashMap<>(); + private final AtomicInteger chunksBeingLoaded = new AtomicInteger(0); @Inject(method = "doPreChunk", at = @At("HEAD")) private void onDoPreChunk(CallbackInfo ci) { @@ -44,8 +37,11 @@ private void onDoPreChunk(CallbackInfo ci) { int x = Math.floorDiv((int) player.posX, 16); int z = Math.floorDiv((int) player.posZ, 16); - // Load chunk asynchronously - CompletableFuture.supplyAsync(() -> loadChunk(x, z), executorService) + // Load chunk asynchronously if it's not already loaded + CompletableFuture + .supplyAsync( + () -> loadedChunks.computeIfAbsent(ChunkPos.asLong(x, z), key -> loadChunk(x, z)), + executorService) .thenAccept(this::onChunkLoaded); } else { setActivePlayerChunksAndCheckLightPublic(); @@ -53,8 +49,7 @@ private void onDoPreChunk(CallbackInfo ci) { } private Chunk loadChunk(int x, int z) { - long key = ChunkPos.asLong(x, z); - Chunk cachedChunk = loadedChunks.get(key); + Chunk cachedChunk = loadedChunks.get(ChunkPos.asLong(x, z)); if (cachedChunk != null) { return cachedChunk; } @@ -66,29 +61,46 @@ private Chunk loadChunk(int x, int z) { chunkProvider.loadChunk(x, z); chunk = chunkProvider.provideChunk(x, z); - // Load adjacent chunks asynchronously + // Load adjacent chunks asynchronously if they're not already loaded for (int i = x - 1; i <= x + 1; i++) { for (int j = z - 1; j <= z + 1; j++) { if (i == x && j == z) { continue; // Skip the current chunk } - int finalI = i; - int finalJ = j; - CompletableFuture.supplyAsync(() -> loadChunk(finalI, finalJ), executorService) - .thenAccept(this::onChunkLoaded); + long key = ChunkPos.asLong(i, j); + if (!loadedChunks.containsKey(key)) { + if (chunksBeingLoaded.incrementAndGet() <= 6) { + int finalI = i; + int finalJ = j; + CompletableFuture + .supplyAsync( + () -> loadedChunks.computeIfAbsent(key, k -> loadChunk(finalI, finalJ)), + executorService) + .thenAccept(this::onChunkLoaded); + } else { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + i--; // Retry current chunk in next batch + chunksBeingLoaded.decrementAndGet(); + } + } } } } // Cache the loaded chunk if (chunk != null) { - loadedChunks.put(key, chunk); + loadedChunks.put(ChunkPos.asLong(x, z), chunk); } return chunk; } private void onChunkLoaded(Chunk chunk) { if (chunk != null) { + chunksBeingLoaded.decrementAndGet(); setActivePlayerChunksAndCheckLightPublic(); } } diff --git a/src/main/java/fr/iamacat/multithreading/mixins/common/core/MixinChunkPopulating.java b/src/main/java/fr/iamacat/multithreading/mixins/common/core/MixinChunkPopulating.java index 5941ddbb..18aaaf54 100644 --- a/src/main/java/fr/iamacat/multithreading/mixins/common/core/MixinChunkPopulating.java +++ b/src/main/java/fr/iamacat/multithreading/mixins/common/core/MixinChunkPopulating.java @@ -3,13 +3,15 @@ import java.util.concurrent.*; import net.minecraft.crash.CrashReport; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.profiler.Profiler; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.management.ServerConfigurationManager; +import net.minecraft.util.MathHelper; import net.minecraft.world.World; import net.minecraft.world.WorldProvider; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.IChunkProvider; -import net.minecraft.world.gen.ChunkProviderServer; import net.minecraft.world.storage.ISaveHandler; import net.minecraft.world.storage.WorldInfo; @@ -25,7 +27,7 @@ @Mixin(value = World.class, priority = 850) public abstract class MixinChunkPopulating { - private final ConcurrentLinkedDeque chunksToPopulate = new ConcurrentLinkedDeque<>(); + private final LinkedBlockingQueue chunksToPopulate = new LinkedBlockingQueue<>(); private final ThreadPoolExecutor executorService = new ThreadPoolExecutor( MultithreadingandtweaksMultithreadingConfig.numberofcpus, @@ -39,11 +41,13 @@ public abstract class MixinChunkPopulating { @Inject(method = "populate", at = @At("TAIL")) private void onPopulate(IChunkProvider chunkProvider, IChunkProvider chunkProvider1, CallbackInfo ci) { if (MultithreadingandtweaksMultithreadingConfig.enableMixinChunkPopulating) { - int maxChunksPerTick = 10; // Change this value to limit the number of chunks to be processed per tick + int maxChunksPerTick = MultithreadingandtweaksMultithreadingConfig.batchsize; // Change this value to limit + // the number of chunks to be + // processed per tick int numChunksProcessed = 0; Chunk chunk = null; while (numChunksProcessed < maxChunksPerTick && (chunk = chunksToPopulate.poll()) != null) { - Chunk finalChunk = chunk; + final Chunk finalChunk = chunk; executorService.execute(() -> { finalChunk.isTerrainPopulated = true; finalChunk.populateChunk(chunkProvider, chunkProvider1, finalChunk.xPosition, finalChunk.zPosition); @@ -58,11 +62,17 @@ private void onPopulate(IChunkProvider chunkProvider, IChunkProvider chunkProvid at = @At("RETURN")) private void onInitialize(MinecraftServer server, ExecutorService executorService, ISaveHandler saveHandler, WorldInfo info, WorldProvider provider, Profiler profiler, CrashReport report, CallbackInfo ci) { - // Only load necessary chunks - for (Object chunk : ((ChunkProviderServer) provider.createChunkGenerator()).loadedChunks) { - if (chunk instanceof Chunk && !((Chunk) chunk).isTerrainPopulated && ((Chunk) chunk).isChunkLoaded) { - Chunk chunkToAdd = (Chunk) chunk; - chunksToPopulate.offer(chunkToAdd); + ServerConfigurationManager playerList = server.getConfigurationManager(); + EntityPlayerMP player = (EntityPlayerMP) playerList.playerEntityList.get(0); + int viewDistance = playerList.getViewDistance() * 16; // Distance in blocks + int playerX = MathHelper.floor_double(player.posX) >> 4; + int playerZ = MathHelper.floor_double(player.posZ) >> 4; + for (int x = playerX - viewDistance; x <= playerX + viewDistance; x++) { + for (int z = playerZ - viewDistance; z <= playerZ + viewDistance; z++) { + Chunk chunk = provider.worldObj.getChunkFromChunkCoords(x, z); + if (!chunk.isTerrainPopulated && chunk.isChunkLoaded) { + chunksToPopulate.offer(chunk); + } } } } diff --git a/src/main/java/fr/iamacat/multithreading/mixins/common/core/MixinGrowthSpreading.java b/src/main/java/fr/iamacat/multithreading/mixins/common/core/MixinGrowthSpreading.java index 7a3c40a9..c5a6facd 100644 --- a/src/main/java/fr/iamacat/multithreading/mixins/common/core/MixinGrowthSpreading.java +++ b/src/main/java/fr/iamacat/multithreading/mixins/common/core/MixinGrowthSpreading.java @@ -9,7 +9,6 @@ import net.minecraft.world.World; 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; @@ -21,9 +20,11 @@ @Mixin(World.class) public abstract class MixinGrowthSpreading { + private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool( MultithreadingandtweaksMultithreadingConfig.numberofcpus, - new ThreadFactoryBuilder().setNameFormat("Growth-Spreading-%d").build()); + new ThreadFactoryBuilder().setNameFormat("Growth-Spreading-%d") + .build()); private World world; private static final int BATCH_SIZE = MultithreadingandtweaksMultithreadingConfig.batchsize; @@ -36,14 +37,13 @@ public abstract class MixinGrowthSpreading { private void onTick(CallbackInfo ci) { this.world = world; // assign world parameter to instance variable if (MultithreadingandtweaksMultithreadingConfig.enableMixinGrowthSpreading) { - growthQueue.clear(); + growthQueue.clear(); } else { addBlocksToGrowthQueue(); processBlocksInGrowthQueue(); } } - private void addBlocksToGrowthQueue() { for (int x = -64; x <= 64; x++) { for (int z = -64; z <= 64; z++) { @@ -67,9 +67,7 @@ private void processBlocksInGrowthQueue() { } if (!batch.isEmpty()) { EXECUTOR.submit(() -> { - batch.forEach(pos -> { - Block block = getBlock(pos.getX(), pos.getY(), pos.getZ()); - }); + batch.forEach(pos -> { Block block = getBlock(pos.getX(), pos.getY(), pos.getZ()); }); batch.clear(); }); } diff --git a/src/main/java/fr/iamacat/multithreading/mixins/common/core/MixinWorldTick.java b/src/main/java/fr/iamacat/multithreading/mixins/common/core/MixinWorldTick.java index e44776d7..1b3617c7 100644 --- a/src/main/java/fr/iamacat/multithreading/mixins/common/core/MixinWorldTick.java +++ b/src/main/java/fr/iamacat/multithreading/mixins/common/core/MixinWorldTick.java @@ -1,7 +1,7 @@ package fr.iamacat.multithreading.mixins.common.core; -import java.util.ArrayList; -import java.util.List; +import java.io.IOException; +import java.util.*; import java.util.concurrent.*; import net.minecraft.world.*; @@ -15,21 +15,22 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import com.google.common.util.concurrent.ThreadFactoryBuilder; - import fr.iamacat.multithreading.config.MultithreadingandtweaksMultithreadingConfig; @Mixin(World.class) public abstract class MixinWorldTick { - private static final int BATCH_SIZE = MultithreadingandtweaksMultithreadingConfig.batchsize; + private static final int updatechunkatonce = 10; private final ConcurrentLinkedQueue chunksToUpdate = new ConcurrentLinkedQueue<>(); + private final Map loadedChunks = new ConcurrentHashMap<>(); + private final Map> loadingChunks = new ConcurrentHashMap<>(); @Final - private ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool( + private ForkJoinPool executorService = new ForkJoinPool( MultithreadingandtweaksMultithreadingConfig.numberofcpus, - new ThreadFactoryBuilder().setNameFormat("World-Tick-%d") - .build()); + ForkJoinPool.defaultForkJoinWorkerThreadFactory, + null, + true); @Inject(method = "tick", at = @At("HEAD")) private void onTick(CallbackInfo ci) { @@ -51,34 +52,85 @@ private void updateChunks(World world) throws Exception { // Add all chunks that need to be updated to the queue for (Object chunk : chunkProvider.loadedChunks) { - chunksToUpdate.offer((Chunk) chunk); + Chunk loadedChunk = (Chunk) chunk; + ChunkCoordIntPair chunkCoord = loadedChunk.getChunkCoordIntPair(); + loadedChunks.put(chunkCoord, loadedChunk); + chunksToUpdate.offer(loadedChunk); } // Process the chunks in batches while (!chunksToUpdate.isEmpty()) { List batch = new ArrayList<>(); - for (int i = 0; i < BATCH_SIZE && !chunksToUpdate.isEmpty(); i++) { + Set adjacentChunks = new HashSet<>(); + for (int i = 0; i < updatechunkatonce && !chunksToUpdate.isEmpty(); i++) { Chunk chunk = chunksToUpdate.poll(); if (chunk != null) { batch.add(chunk); + ChunkCoordIntPair chunkCoord = chunk.getChunkCoordIntPair(); + adjacentChunks.addAll(getAdjacentChunks(chunkCoord)); + } + } + + // Load the adjacent chunks if they are not already loaded + for (ChunkCoordIntPair chunkCoord : adjacentChunks) { + if (!loadedChunks.containsKey(chunkCoord) && !loadingChunks.containsKey(chunkCoord)) { + loadingChunks.put(chunkCoord, CompletableFuture.supplyAsync(() -> { + try { + Chunk adjacentChunk = chunkLoader + .loadChunk(world, chunkCoord.chunkXPos, chunkCoord.chunkZPos); + loadedChunks.put(chunkCoord, adjacentChunk); + return adjacentChunk; + } catch (IOException e) { + throw new RuntimeException(e); + } + }, executorService)); } } - processBatch(chunkProvider, chunkLoader, batch); + + // Wait for the adjacent chunks to finish loading + for (CompletableFuture future : loadingChunks.values()) { + Chunk adjacentChunk = future.join(); + if (adjacentChunk != null) { + loadedChunks.put(adjacentChunk.getChunkCoordIntPair(), adjacentChunk); + } + } + loadingChunks.clear(); + + // Process the batch of chunks + // processBatch(chunkProvider, batch); } } - private void processBatch(ChunkProviderServer chunkProvider, IChunkLoader chunkLoader, List batch) { + private Set getAdjacentChunks(ChunkCoordIntPair chunkCoord) { + Set adjacentChunks = new HashSet<>(); + int chunkX = chunkCoord.chunkXPos; + int chunkZ = chunkCoord.chunkZPos; + int radius = 1; + + for (int x = chunkX - radius; x <= chunkX + radius; x++) { + for (int z = chunkZ - radius; z <= chunkZ + radius; z++) { + if (x != chunkX || z != chunkZ) { + adjacentChunks.add(new ChunkCoordIntPair(x, z)); + } + } + } + return adjacentChunks; + } + private final Object lock = new Object(); + private void processBatch(ChunkProviderServer chunkProvider, List batch) { // Load the chunks from disk asynchronously and save them CompletableFuture.runAsync(() -> { batch.parallelStream() .forEach(chunk -> { - synchronized (chunk) { - synchronized (chunkLoader) { - WorldProvider worldProvider = chunkProvider.worldObj.provider; - chunkLoader.saveExtraChunkData(worldProvider.worldObj, chunk); - } + synchronized (lock) { + chunkProvider.saveChunks(true, null); } }); + // Save all loaded chunks + synchronized (lock) { + chunkProvider.saveChunks(true, null); + } }, executorService); } + }