From b8ae9487962211d74c0163f969efdf0337799c32 Mon Sep 17 00:00:00 2001 From: Bud Date: Wed, 25 Nov 2020 14:37:46 -0600 Subject: [PATCH 1/7] Async pop API --- .../population/AsyncGaeaBlockPopulator.java | 14 +++ .../population/AsyncPopulationReturn.java | 40 +++++++++ .../gaea/population/BlockCoordinate.java | 38 ++++++++ .../gaea/population/PopulationManager.java | 87 +++++++++++++++++-- 4 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/polydev/gaea/population/AsyncGaeaBlockPopulator.java create mode 100644 src/main/java/org/polydev/gaea/population/AsyncPopulationReturn.java create mode 100644 src/main/java/org/polydev/gaea/population/BlockCoordinate.java diff --git a/src/main/java/org/polydev/gaea/population/AsyncGaeaBlockPopulator.java b/src/main/java/org/polydev/gaea/population/AsyncGaeaBlockPopulator.java new file mode 100644 index 0000000..e91fe3e --- /dev/null +++ b/src/main/java/org/polydev/gaea/population/AsyncGaeaBlockPopulator.java @@ -0,0 +1,14 @@ +package org.polydev.gaea.population; + +import org.bukkit.Chunk; +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.Random; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; + +public abstract class AsyncGaeaBlockPopulator { + public abstract CompletableFuture populate(@NotNull World world, @NotNull Random random, Chunk chunk, int id); +} diff --git a/src/main/java/org/polydev/gaea/population/AsyncPopulationReturn.java b/src/main/java/org/polydev/gaea/population/AsyncPopulationReturn.java new file mode 100644 index 0000000..fb945fa --- /dev/null +++ b/src/main/java/org/polydev/gaea/population/AsyncPopulationReturn.java @@ -0,0 +1,40 @@ +package org.polydev.gaea.population; + +import java.util.HashSet; +import java.util.UUID; + +public class AsyncPopulationReturn { + private final HashSet changeList; + private final int populatorId; + private final UUID worldID; + private final int chunkX; + private final int chunkZ; + + public AsyncPopulationReturn(HashSet changeList, int populatorId, UUID worldID, int chunkX, int chunkZ) { + this.changeList = changeList; + this.populatorId = populatorId; + this.worldID = worldID; + this.chunkX = chunkX; + this.chunkZ = chunkZ; + } + + public HashSet getChangeList() { + return changeList; + } + + public int getPopulatorId() { + return populatorId; + } + + public UUID getWorldID() { + return worldID; + } + + public int getChunkX() { + return chunkX; + } + + public int getChunkZ() { + return chunkZ; + } +} diff --git a/src/main/java/org/polydev/gaea/population/BlockCoordinate.java b/src/main/java/org/polydev/gaea/population/BlockCoordinate.java new file mode 100644 index 0000000..7ffa67f --- /dev/null +++ b/src/main/java/org/polydev/gaea/population/BlockCoordinate.java @@ -0,0 +1,38 @@ +package org.polydev.gaea.population; + +import org.bukkit.block.data.BlockData; + +import java.util.UUID; + +public class BlockCoordinate { + private final int x; + private final int y; + private final int z; + + private final BlockData blockData; + + public BlockCoordinate(int x, int y, int z, BlockData blockData) { + this.x = x; + this.y = y; + this.z = z; + + this.blockData = blockData; + } + + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getZ() { + return z; + } + + public BlockData getBlockData() { + return blockData; + } +} diff --git a/src/main/java/org/polydev/gaea/population/PopulationManager.java b/src/main/java/org/polydev/gaea/population/PopulationManager.java index 6741886..27457c0 100644 --- a/src/main/java/org/polydev/gaea/population/PopulationManager.java +++ b/src/main/java/org/polydev/gaea/population/PopulationManager.java @@ -1,7 +1,10 @@ package org.polydev.gaea.population; +import net.jafama.FastMath; import org.bukkit.Chunk; +import org.bukkit.Location; import org.bukkit.World; +import org.bukkit.block.Block; import org.bukkit.generator.BlockPopulator; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; @@ -14,13 +17,16 @@ import java.io.File; import java.io.IOException; -import java.util.HashSet; -import java.util.List; -import java.util.Random; +import java.util.*; +import java.util.concurrent.CompletableFuture; public class PopulationManager extends BlockPopulator { private final List attachedPopulators = new GlueList<>(); + private final List attachedAsyncPopulators = new GlueList<>(); private final HashSet needsPop = new HashSet<>(); + private final GlueList> needsAsyncPop = new GlueList<>(); + private final GlueList> doingAsyncPop = new GlueList<>(); + private final HashSet> workingAsyncPopulators = new HashSet<>(); private final JavaPlugin main; private final Object popLock = new Object(); private WorldProfiler profiler; @@ -33,10 +39,68 @@ public void attach(GaeaBlockPopulator populator) { this.attachedPopulators.add(populator); } + public void attach(AsyncGaeaBlockPopulator populator) { + this.attachedAsyncPopulators.add(populator); + this.needsAsyncPop.add(new HashSet<>()); + } + + public void asyncPopulate(World world) { + for (CompletableFuture c : workingAsyncPopulators) { + if (c.isDone()) { + AsyncPopulationReturn data = c.join(); + Integer chunkX = data.getChunkX(); + Integer chunkY = data.getChunkZ(); + ChunkCoordinate chunkCoordinate = new ChunkCoordinate(chunkX, chunkY, data.getWorldID()); + for (int i = 0; i < needsAsyncPop.size(); i++) { + if (needsAsyncPop.get(i).contains(chunkCoordinate) && i < data.getPopulatorId()) { + continue; + } + } + HashSet blockList = data.getChangeList(); + Chunk chunk = null; + if(world.getUID() != data.getWorldID()) { return; } + for (BlockCoordinate b: blockList) { + if (chunkX == null) { + chunkX = FastMath.floorMod(b.getX(), 16); + chunkY = FastMath.floorMod(b.getY(), 16); + } + if(chunk == null) { + if(world.isChunkLoaded(chunkX, chunkY)) { + continue; + } + chunk = world.getChunkAt(chunkX, chunkY); + } + Block block = chunk.getBlock(chunkX, b.getY(), chunkY); + block.setBlockData(b.getBlockData(), false); + } + if(chunk == null) { + needsAsyncPop.get(data.getPopulatorId()).add(chunkCoordinate); + } + doingAsyncPop.get(data.getPopulatorId()).remove(chunkCoordinate); + workingAsyncPopulators.remove(c); + } + } + for (int i = 0; i < needsAsyncPop.size(); i++) { + for (ChunkCoordinate c: needsAsyncPop.get(i)) { + if (world.isChunkLoaded(c.getX(), c.getZ())) { + Random random = new FastRandom(world.getSeed()); + long xRand = (random.nextLong() / 2L << 1L) + 1L; + long zRand = (random.nextLong() / 2L << 1L) + 1L; + random.setSeed((long) c.getX() * xRand + (long) c.getZ() * zRand ^ world.getSeed()); + Chunk currentChunk = world.getChunkAt(c.getX(), c.getZ()); + workingAsyncPopulators.add(attachedAsyncPopulators.get(i).populate(world, random, currentChunk, i)); + } + } + } + } + @Override public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) { try(ProfileFuture ignored = measure()) { needsPop.add(new ChunkCoordinate(chunk)); + for(HashSet h : needsAsyncPop) { + h.add(new ChunkCoordinate(chunk)); + } int x = chunk.getX(); int z = chunk.getZ(); if(main.isEnabled()) { @@ -64,12 +128,22 @@ public synchronized void saveBlocks(World w) throws IOException { File f = new File(Gaea.getGaeaFolder(w), "chunks.bin"); f.createNewFile(); SerializationUtil.toFile((HashSet) needsPop.clone(), f); + for (int i = 0; i < doingAsyncPop.size(); i++) { + for (ChunkCoordinate c: doingAsyncPop.get(i)) { + needsAsyncPop.get(i).add(c); + } + } + f = new File(Gaea.getGaeaFolder(w), "chunksasync.bin"); + f.createNewFile(); + SerializationUtil.toFile((GlueList>) needsAsyncPop.clone(), f); } @SuppressWarnings("unchecked") public synchronized void loadBlocks(World w) throws IOException, ClassNotFoundException { File f = new File(Gaea.getGaeaFolder(w), "chunks.bin"); needsPop.addAll((HashSet) SerializationUtil.fromFile(f)); + f = new File(Gaea.getGaeaFolder(w), "chunksasync.bin"); + needsAsyncPop.addAll((GlueList>) SerializationUtil.fromFile(f)); } @@ -85,10 +159,13 @@ public synchronized void checkNeighbors(int x, int z, World w) { long zRand = (random.nextLong() / 2L << 1L) + 1L; random.setSeed((long) x * xRand + (long) z * zRand ^ w.getSeed()); Chunk currentChunk = w.getChunkAt(x, z); - for(GaeaBlockPopulator r : attachedPopulators) { - r.populate(w, random, currentChunk); + for (int i = 0; i < attachedPopulators.size(); i++) { + attachedPopulators.get(i).populate(w, random, currentChunk); } needsPop.remove(c); + for (int i = 0; i < needsAsyncPop.size(); i++) { + needsAsyncPop.get(i).add(c); + } } } } \ No newline at end of file From 799de0ca4162122afdffacafd0d54f1d16d5b515 Mon Sep 17 00:00:00 2001 From: Bud Date: Wed, 25 Nov 2020 14:41:43 -0600 Subject: [PATCH 2/7] Clean Up and GC --- .../org/polydev/gaea/population/PopulationManager.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/polydev/gaea/population/PopulationManager.java b/src/main/java/org/polydev/gaea/population/PopulationManager.java index 27457c0..92de182 100644 --- a/src/main/java/org/polydev/gaea/population/PopulationManager.java +++ b/src/main/java/org/polydev/gaea/population/PopulationManager.java @@ -66,6 +66,9 @@ public void asyncPopulate(World world) { } if(chunk == null) { if(world.isChunkLoaded(chunkX, chunkY)) { + if (workingAsyncPopulators.size() >= 64) { + workingAsyncPopulators.remove(c); + } continue; } chunk = world.getChunkAt(chunkX, chunkY); @@ -159,8 +162,8 @@ public synchronized void checkNeighbors(int x, int z, World w) { long zRand = (random.nextLong() / 2L << 1L) + 1L; random.setSeed((long) x * xRand + (long) z * zRand ^ w.getSeed()); Chunk currentChunk = w.getChunkAt(x, z); - for (int i = 0; i < attachedPopulators.size(); i++) { - attachedPopulators.get(i).populate(w, random, currentChunk); + for(GaeaBlockPopulator r : attachedPopulators) { + r.populate(w, random, currentChunk); } needsPop.remove(c); for (int i = 0; i < needsAsyncPop.size(); i++) { From 5696d2706ecf17e46ee5936cc752b4b065d0e223 Mon Sep 17 00:00:00 2001 From: Bud Date: Wed, 25 Nov 2020 14:48:37 -0600 Subject: [PATCH 3/7] More Clean up --- .../org/polydev/gaea/population/PopulationManager.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/polydev/gaea/population/PopulationManager.java b/src/main/java/org/polydev/gaea/population/PopulationManager.java index 92de182..60d76aa 100644 --- a/src/main/java/org/polydev/gaea/population/PopulationManager.java +++ b/src/main/java/org/polydev/gaea/population/PopulationManager.java @@ -48,8 +48,8 @@ public void asyncPopulate(World world) { for (CompletableFuture c : workingAsyncPopulators) { if (c.isDone()) { AsyncPopulationReturn data = c.join(); - Integer chunkX = data.getChunkX(); - Integer chunkY = data.getChunkZ(); + int chunkX = data.getChunkX(); + int chunkY = data.getChunkZ(); ChunkCoordinate chunkCoordinate = new ChunkCoordinate(chunkX, chunkY, data.getWorldID()); for (int i = 0; i < needsAsyncPop.size(); i++) { if (needsAsyncPop.get(i).contains(chunkCoordinate) && i < data.getPopulatorId()) { @@ -60,10 +60,6 @@ public void asyncPopulate(World world) { Chunk chunk = null; if(world.getUID() != data.getWorldID()) { return; } for (BlockCoordinate b: blockList) { - if (chunkX == null) { - chunkX = FastMath.floorMod(b.getX(), 16); - chunkY = FastMath.floorMod(b.getY(), 16); - } if(chunk == null) { if(world.isChunkLoaded(chunkX, chunkY)) { if (workingAsyncPopulators.size() >= 64) { From b930b04592ba3b044397471d58df7296c8b2bb88 Mon Sep 17 00:00:00 2001 From: Bud Date: Wed, 25 Nov 2020 14:54:10 -0600 Subject: [PATCH 4/7] Perf Fixes and Sync --- .../gaea/population/PopulationManager.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/polydev/gaea/population/PopulationManager.java b/src/main/java/org/polydev/gaea/population/PopulationManager.java index 60d76aa..76ac9f7 100644 --- a/src/main/java/org/polydev/gaea/population/PopulationManager.java +++ b/src/main/java/org/polydev/gaea/population/PopulationManager.java @@ -44,7 +44,7 @@ public void attach(AsyncGaeaBlockPopulator populator) { this.needsAsyncPop.add(new HashSet<>()); } - public void asyncPopulate(World world) { + public synchronized void asyncPopulate(World world) { for (CompletableFuture c : workingAsyncPopulators) { if (c.isDone()) { AsyncPopulationReturn data = c.join(); @@ -79,13 +79,13 @@ public void asyncPopulate(World world) { workingAsyncPopulators.remove(c); } } + Random random = new FastRandom(world.getSeed()); + long xRand = (random.nextLong() / 2L << 1L) + 1L; + long zRand = (random.nextLong() / 2L << 1L) + 1L; for (int i = 0; i < needsAsyncPop.size(); i++) { for (ChunkCoordinate c: needsAsyncPop.get(i)) { if (world.isChunkLoaded(c.getX(), c.getZ())) { - Random random = new FastRandom(world.getSeed()); - long xRand = (random.nextLong() / 2L << 1L) + 1L; - long zRand = (random.nextLong() / 2L << 1L) + 1L; - random.setSeed((long) c.getX() * xRand + (long) c.getZ() * zRand ^ world.getSeed()); + Random chunkRandom = new FastRandom((long) c.getX() * xRand + (long) c.getZ() * zRand ^ world.getSeed()); Chunk currentChunk = world.getChunkAt(c.getX(), c.getZ()); workingAsyncPopulators.add(attachedAsyncPopulators.get(i).populate(world, random, currentChunk, i)); } @@ -96,9 +96,10 @@ public void asyncPopulate(World world) { @Override public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) { try(ProfileFuture ignored = measure()) { - needsPop.add(new ChunkCoordinate(chunk)); + ChunkCoordinate chunkCoordinate = new ChunkCoordinate(chunk); + needsPop.add(chunkCoordinate); for(HashSet h : needsAsyncPop) { - h.add(new ChunkCoordinate(chunk)); + h.add(chunkCoordinate); } int x = chunk.getX(); int z = chunk.getZ(); From cfff14b52f48b3bdd7d8dc9b31ac9ba52b63340d Mon Sep 17 00:00:00 2001 From: Bud Date: Wed, 25 Nov 2020 15:07:09 -0600 Subject: [PATCH 5/7] Faster HashSets and Fix GC --- build.gradle.kts | 1 + .../gaea/population/PopulationManager.java | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e49a3e1..c3c9169 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { implementation("co.aikar:taskchain-bukkit:3.7.2") implementation("com.esotericsoftware:reflectasm:1.11.9") implementation("org.bstats:bstats-bukkit:1.7") + implementation("it.unimi.dsi:fastutil:8.4.3") // JUnit. testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0") diff --git a/src/main/java/org/polydev/gaea/population/PopulationManager.java b/src/main/java/org/polydev/gaea/population/PopulationManager.java index 76ac9f7..3c0eafe 100644 --- a/src/main/java/org/polydev/gaea/population/PopulationManager.java +++ b/src/main/java/org/polydev/gaea/population/PopulationManager.java @@ -1,5 +1,6 @@ package org.polydev.gaea.population; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.jafama.FastMath; import org.bukkit.Chunk; import org.bukkit.Location; @@ -23,10 +24,10 @@ public class PopulationManager extends BlockPopulator { private final List attachedPopulators = new GlueList<>(); private final List attachedAsyncPopulators = new GlueList<>(); - private final HashSet needsPop = new HashSet<>(); - private final GlueList> needsAsyncPop = new GlueList<>(); - private final GlueList> doingAsyncPop = new GlueList<>(); - private final HashSet> workingAsyncPopulators = new HashSet<>(); + private final ObjectOpenHashSet needsPop = new ObjectOpenHashSet<>(); + private final GlueList> needsAsyncPop = new GlueList<>(); + private final GlueList> doingAsyncPop = new GlueList<>(); + private final ObjectOpenHashSet> workingAsyncPopulators = new ObjectOpenHashSet<>(); private final JavaPlugin main; private final Object popLock = new Object(); private WorldProfiler profiler; @@ -41,7 +42,7 @@ public void attach(GaeaBlockPopulator populator) { public void attach(AsyncGaeaBlockPopulator populator) { this.attachedAsyncPopulators.add(populator); - this.needsAsyncPop.add(new HashSet<>()); + this.needsAsyncPop.add(new ObjectOpenHashSet<>()); } public synchronized void asyncPopulate(World world) { @@ -63,7 +64,9 @@ public synchronized void asyncPopulate(World world) { if(chunk == null) { if(world.isChunkLoaded(chunkX, chunkY)) { if (workingAsyncPopulators.size() >= 64) { + needsAsyncPop.get(data.getPopulatorId()).add(chunkCoordinate); workingAsyncPopulators.remove(c); + workingAsyncPopulators.trim(); } continue; } @@ -98,7 +101,7 @@ public void populate(@NotNull World world, @NotNull Random random, @NotNull Chun try(ProfileFuture ignored = measure()) { ChunkCoordinate chunkCoordinate = new ChunkCoordinate(chunk); needsPop.add(chunkCoordinate); - for(HashSet h : needsAsyncPop) { + for(ObjectOpenHashSet h : needsAsyncPop) { h.add(chunkCoordinate); } int x = chunk.getX(); @@ -127,7 +130,7 @@ public void attachProfiler(WorldProfiler p) { public synchronized void saveBlocks(World w) throws IOException { File f = new File(Gaea.getGaeaFolder(w), "chunks.bin"); f.createNewFile(); - SerializationUtil.toFile((HashSet) needsPop.clone(), f); + SerializationUtil.toFile((ObjectOpenHashSet) needsPop.clone(), f); for (int i = 0; i < doingAsyncPop.size(); i++) { for (ChunkCoordinate c: doingAsyncPop.get(i)) { needsAsyncPop.get(i).add(c); @@ -143,7 +146,7 @@ public synchronized void loadBlocks(World w) throws IOException, ClassNotFoundEx File f = new File(Gaea.getGaeaFolder(w), "chunks.bin"); needsPop.addAll((HashSet) SerializationUtil.fromFile(f)); f = new File(Gaea.getGaeaFolder(w), "chunksasync.bin"); - needsAsyncPop.addAll((GlueList>) SerializationUtil.fromFile(f)); + needsAsyncPop.addAll((GlueList>) SerializationUtil.fromFile(f)); } From a2f076b349ff67681edb7fa480b8349a70f38d99 Mon Sep 17 00:00:00 2001 From: Bud Date: Wed, 25 Nov 2020 16:41:47 -0600 Subject: [PATCH 6/7] Refractor and Run Async --- src/main/java/org/polydev/gaea/Gaea.java | 10 ++ .../population/AsyncPopulationManager.java | 106 ++++++++++++++++++ .../gaea/population/PopulationManager.java | 84 +------------- 3 files changed, 119 insertions(+), 81 deletions(-) create mode 100644 src/main/java/org/polydev/gaea/population/AsyncPopulationManager.java diff --git a/src/main/java/org/polydev/gaea/Gaea.java b/src/main/java/org/polydev/gaea/Gaea.java index 2eda876..1b22fcb 100644 --- a/src/main/java/org/polydev/gaea/Gaea.java +++ b/src/main/java/org/polydev/gaea/Gaea.java @@ -1,14 +1,22 @@ package org.polydev.gaea; import org.bstats.bukkit.Metrics; +import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.plugin.java.JavaPlugin; +import org.polydev.gaea.population.AsyncPopulationManager; +import org.polydev.gaea.population.PopulationManager; import java.io.File; public class Gaea extends JavaPlugin { private static boolean debug; + private static Gaea instance; + + public static Gaea getInstance() { + return instance; + } @Override public void onDisable() { @@ -17,11 +25,13 @@ public void onDisable() { @Override public void onEnable() { + instance = this; super.onEnable(); Metrics metrics = new Metrics(this, 9092); saveDefaultConfig(); reloadConfig(); FileConfiguration configuration = getConfig(); + Bukkit.getScheduler().scheduleSyncRepeatingTask(this, AsyncPopulationManager::asyncPopulate, 1, 1); debug = configuration.getBoolean("debug", false); } diff --git a/src/main/java/org/polydev/gaea/population/AsyncPopulationManager.java b/src/main/java/org/polydev/gaea/population/AsyncPopulationManager.java new file mode 100644 index 0000000..fdead56 --- /dev/null +++ b/src/main/java/org/polydev/gaea/population/AsyncPopulationManager.java @@ -0,0 +1,106 @@ +package org.polydev.gaea.population; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.polydev.gaea.Gaea; +import org.polydev.gaea.util.FastRandom; +import org.polydev.gaea.util.GlueList; +import org.polydev.gaea.util.SerializationUtil; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CompletableFuture; + +public class AsyncPopulationManager { + private static final List attachedAsyncPopulators = new GlueList<>(); + private static final GlueList> needsAsyncPop = new GlueList<>(); + private static final GlueList> doingAsyncPop = new GlueList<>(); + private static final ObjectOpenHashSet> workingAsyncPopulators = new ObjectOpenHashSet<>(); + + public void attach(AsyncGaeaBlockPopulator populator) { + this.attachedAsyncPopulators.add(populator); + this.needsAsyncPop.add(new ObjectOpenHashSet<>()); + } + + public synchronized void saveBlocks(World w) throws IOException { + for (int i = 0; i < doingAsyncPop.size(); i++) { + for (ChunkCoordinate c: doingAsyncPop.get(i)) { + needsAsyncPop.get(i).add(c); + } + } + File f = new File(Gaea.getGaeaFolder(w), "chunksasync.bin"); + f.createNewFile(); + SerializationUtil.toFile((GlueList>) needsAsyncPop.clone(), f); + } + + @SuppressWarnings("unchecked") + public synchronized void loadBlocks(World w) throws IOException, ClassNotFoundException { + File f = new File(Gaea.getGaeaFolder(w), "chunksasync.bin"); + needsAsyncPop.addAll((GlueList>) SerializationUtil.fromFile(f)); + } + + public static synchronized void addChunk(ChunkCoordinate c) { + for (ObjectOpenHashSet h: needsAsyncPop) { + h.add(c); + } + } + + public static synchronized void asyncPopulate() { + for (CompletableFuture c : workingAsyncPopulators) { + if (c.isDone()) { + AsyncPopulationReturn data = c.join(); + int chunkX = data.getChunkX(); + int chunkY = data.getChunkZ(); + ChunkCoordinate chunkCoordinate = new ChunkCoordinate(chunkX, chunkY, data.getWorldID()); + for (int i = 0; i < needsAsyncPop.size(); i++) { + if (needsAsyncPop.get(i).contains(chunkCoordinate) && i < data.getPopulatorId()) { + continue; + } + } + HashSet blockList = data.getChangeList(); + Chunk chunk = null; + World world = Bukkit.getWorld(data.getWorldID()); + if(world.getUID() != data.getWorldID()) { return; } + for (BlockCoordinate b: blockList) { + if(chunk == null) { + if(world.isChunkLoaded(chunkX, chunkY)) { + if (workingAsyncPopulators.size() >= 64) { + needsAsyncPop.get(data.getPopulatorId()).add(chunkCoordinate); + workingAsyncPopulators.remove(c); + workingAsyncPopulators.trim(); + } + continue; + } + chunk = world.getChunkAt(chunkX, chunkY); + } + Block block = chunk.getBlock(chunkX, b.getY(), chunkY); + block.setBlockData(b.getBlockData(), false); + } + if(chunk == null) { + needsAsyncPop.get(data.getPopulatorId()).add(chunkCoordinate); + } + doingAsyncPop.get(data.getPopulatorId()).remove(chunkCoordinate); + workingAsyncPopulators.remove(c); + } + } + for (int i = 0; i < needsAsyncPop.size(); i++) { + for (ChunkCoordinate c: needsAsyncPop.get(i)) { + World world = Bukkit.getWorld(c.getWorldID()); + if (world.isChunkLoaded(c.getX(), c.getZ())) { + Random random = new FastRandom(world.getSeed()); + long xRand = (random.nextLong() / 2L << 1L) + 1L; + long zRand = (random.nextLong() / 2L << 1L) + 1L; + Random chunkRandom = new FastRandom((long) c.getX() * xRand + (long) c.getZ() * zRand ^ world.getSeed()); + Chunk currentChunk = world.getChunkAt(c.getX(), c.getZ()); + workingAsyncPopulators.add(attachedAsyncPopulators.get(i).populate(world, random, currentChunk, i)); + } + } + } + } +} diff --git a/src/main/java/org/polydev/gaea/population/PopulationManager.java b/src/main/java/org/polydev/gaea/population/PopulationManager.java index 3c0eafe..af8a809 100644 --- a/src/main/java/org/polydev/gaea/population/PopulationManager.java +++ b/src/main/java/org/polydev/gaea/population/PopulationManager.java @@ -1,11 +1,9 @@ package org.polydev.gaea.population; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import net.jafama.FastMath; +import org.bukkit.Bukkit; import org.bukkit.Chunk; -import org.bukkit.Location; import org.bukkit.World; -import org.bukkit.block.Block; import org.bukkit.generator.BlockPopulator; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; @@ -19,15 +17,10 @@ import java.io.File; import java.io.IOException; import java.util.*; -import java.util.concurrent.CompletableFuture; public class PopulationManager extends BlockPopulator { - private final List attachedPopulators = new GlueList<>(); - private final List attachedAsyncPopulators = new GlueList<>(); + private final List attachedPopulators = new GlueList(); private final ObjectOpenHashSet needsPop = new ObjectOpenHashSet<>(); - private final GlueList> needsAsyncPop = new GlueList<>(); - private final GlueList> doingAsyncPop = new GlueList<>(); - private final ObjectOpenHashSet> workingAsyncPopulators = new ObjectOpenHashSet<>(); private final JavaPlugin main; private final Object popLock = new Object(); private WorldProfiler profiler; @@ -40,70 +33,11 @@ public void attach(GaeaBlockPopulator populator) { this.attachedPopulators.add(populator); } - public void attach(AsyncGaeaBlockPopulator populator) { - this.attachedAsyncPopulators.add(populator); - this.needsAsyncPop.add(new ObjectOpenHashSet<>()); - } - - public synchronized void asyncPopulate(World world) { - for (CompletableFuture c : workingAsyncPopulators) { - if (c.isDone()) { - AsyncPopulationReturn data = c.join(); - int chunkX = data.getChunkX(); - int chunkY = data.getChunkZ(); - ChunkCoordinate chunkCoordinate = new ChunkCoordinate(chunkX, chunkY, data.getWorldID()); - for (int i = 0; i < needsAsyncPop.size(); i++) { - if (needsAsyncPop.get(i).contains(chunkCoordinate) && i < data.getPopulatorId()) { - continue; - } - } - HashSet blockList = data.getChangeList(); - Chunk chunk = null; - if(world.getUID() != data.getWorldID()) { return; } - for (BlockCoordinate b: blockList) { - if(chunk == null) { - if(world.isChunkLoaded(chunkX, chunkY)) { - if (workingAsyncPopulators.size() >= 64) { - needsAsyncPop.get(data.getPopulatorId()).add(chunkCoordinate); - workingAsyncPopulators.remove(c); - workingAsyncPopulators.trim(); - } - continue; - } - chunk = world.getChunkAt(chunkX, chunkY); - } - Block block = chunk.getBlock(chunkX, b.getY(), chunkY); - block.setBlockData(b.getBlockData(), false); - } - if(chunk == null) { - needsAsyncPop.get(data.getPopulatorId()).add(chunkCoordinate); - } - doingAsyncPop.get(data.getPopulatorId()).remove(chunkCoordinate); - workingAsyncPopulators.remove(c); - } - } - Random random = new FastRandom(world.getSeed()); - long xRand = (random.nextLong() / 2L << 1L) + 1L; - long zRand = (random.nextLong() / 2L << 1L) + 1L; - for (int i = 0; i < needsAsyncPop.size(); i++) { - for (ChunkCoordinate c: needsAsyncPop.get(i)) { - if (world.isChunkLoaded(c.getX(), c.getZ())) { - Random chunkRandom = new FastRandom((long) c.getX() * xRand + (long) c.getZ() * zRand ^ world.getSeed()); - Chunk currentChunk = world.getChunkAt(c.getX(), c.getZ()); - workingAsyncPopulators.add(attachedAsyncPopulators.get(i).populate(world, random, currentChunk, i)); - } - } - } - } - @Override public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) { try(ProfileFuture ignored = measure()) { ChunkCoordinate chunkCoordinate = new ChunkCoordinate(chunk); needsPop.add(chunkCoordinate); - for(ObjectOpenHashSet h : needsAsyncPop) { - h.add(chunkCoordinate); - } int x = chunk.getX(); int z = chunk.getZ(); if(main.isEnabled()) { @@ -131,22 +65,12 @@ public synchronized void saveBlocks(World w) throws IOException { File f = new File(Gaea.getGaeaFolder(w), "chunks.bin"); f.createNewFile(); SerializationUtil.toFile((ObjectOpenHashSet) needsPop.clone(), f); - for (int i = 0; i < doingAsyncPop.size(); i++) { - for (ChunkCoordinate c: doingAsyncPop.get(i)) { - needsAsyncPop.get(i).add(c); - } - } - f = new File(Gaea.getGaeaFolder(w), "chunksasync.bin"); - f.createNewFile(); - SerializationUtil.toFile((GlueList>) needsAsyncPop.clone(), f); } @SuppressWarnings("unchecked") public synchronized void loadBlocks(World w) throws IOException, ClassNotFoundException { File f = new File(Gaea.getGaeaFolder(w), "chunks.bin"); needsPop.addAll((HashSet) SerializationUtil.fromFile(f)); - f = new File(Gaea.getGaeaFolder(w), "chunksasync.bin"); - needsAsyncPop.addAll((GlueList>) SerializationUtil.fromFile(f)); } @@ -166,9 +90,7 @@ public synchronized void checkNeighbors(int x, int z, World w) { r.populate(w, random, currentChunk); } needsPop.remove(c); - for (int i = 0; i < needsAsyncPop.size(); i++) { - needsAsyncPop.get(i).add(c); - } + AsyncPopulationManager.addChunk(c); } } } \ No newline at end of file From 324d098cc30de2ee8b2ff44c13a5d4b10f0be3f6 Mon Sep 17 00:00:00 2001 From: Bud Date: Wed, 25 Nov 2020 19:57:19 -0600 Subject: [PATCH 7/7] limit threads and fixes --- .../polydev/gaea/population/AsyncGaeaBlockPopulator.java | 3 ++- .../org/polydev/gaea/population/AsyncPopulationManager.java | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/polydev/gaea/population/AsyncGaeaBlockPopulator.java b/src/main/java/org/polydev/gaea/population/AsyncGaeaBlockPopulator.java index e91fe3e..84d7ffc 100644 --- a/src/main/java/org/polydev/gaea/population/AsyncGaeaBlockPopulator.java +++ b/src/main/java/org/polydev/gaea/population/AsyncGaeaBlockPopulator.java @@ -1,6 +1,7 @@ package org.polydev.gaea.population; import org.bukkit.Chunk; +import org.bukkit.ChunkSnapshot; import org.bukkit.World; import org.jetbrains.annotations.NotNull; @@ -10,5 +11,5 @@ import java.util.concurrent.CompletableFuture; public abstract class AsyncGaeaBlockPopulator { - public abstract CompletableFuture populate(@NotNull World world, @NotNull Random random, Chunk chunk, int id); + public abstract CompletableFuture populate(@NotNull World world, @NotNull Random random, ChunkSnapshot chunk, int id); } diff --git a/src/main/java/org/polydev/gaea/population/AsyncPopulationManager.java b/src/main/java/org/polydev/gaea/population/AsyncPopulationManager.java index fdead56..4158487 100644 --- a/src/main/java/org/polydev/gaea/population/AsyncPopulationManager.java +++ b/src/main/java/org/polydev/gaea/population/AsyncPopulationManager.java @@ -3,6 +3,7 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.bukkit.Bukkit; import org.bukkit.Chunk; +import org.bukkit.ChunkSnapshot; import org.bukkit.World; import org.bukkit.block.Block; import org.polydev.gaea.Gaea; @@ -92,13 +93,14 @@ public static synchronized void asyncPopulate() { for (int i = 0; i < needsAsyncPop.size(); i++) { for (ChunkCoordinate c: needsAsyncPop.get(i)) { World world = Bukkit.getWorld(c.getWorldID()); - if (world.isChunkLoaded(c.getX(), c.getZ())) { + if (world.isChunkLoaded(c.getX(), c.getZ()) && workingAsyncPopulators.size() <= 8) { Random random = new FastRandom(world.getSeed()); long xRand = (random.nextLong() / 2L << 1L) + 1L; long zRand = (random.nextLong() / 2L << 1L) + 1L; Random chunkRandom = new FastRandom((long) c.getX() * xRand + (long) c.getZ() * zRand ^ world.getSeed()); Chunk currentChunk = world.getChunkAt(c.getX(), c.getZ()); - workingAsyncPopulators.add(attachedAsyncPopulators.get(i).populate(world, random, currentChunk, i)); + ChunkSnapshot snapshot = currentChunk.getChunkSnapshot(true, true, false); + workingAsyncPopulators.add(attachedAsyncPopulators.get(i).populate(world, chunkRandom, snapshot, i)); } } }