Skip to content
This repository has been archived by the owner on Mar 6, 2021. It is now read-only.

[WIP] Add Support for Async Populators #14

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/polydev/gaea/Gaea.java
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.polydev.gaea.population;

import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot;
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<AsyncPopulationReturn> populate(@NotNull World world, @NotNull Random random, ChunkSnapshot chunk, int id);
}
108 changes: 108 additions & 0 deletions src/main/java/org/polydev/gaea/population/AsyncPopulationManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package org.polydev.gaea.population;

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;
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<AsyncGaeaBlockPopulator> attachedAsyncPopulators = new GlueList<>();
private static final GlueList<ObjectOpenHashSet<ChunkCoordinate>> needsAsyncPop = new GlueList<>();
private static final GlueList<ObjectOpenHashSet<ChunkCoordinate>> doingAsyncPop = new GlueList<>();
private static final ObjectOpenHashSet<CompletableFuture<AsyncPopulationReturn>> 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<HashSet<ChunkCoordinate>>) 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<ObjectOpenHashSet<ChunkCoordinate>>) SerializationUtil.fromFile(f));
}

public static synchronized void addChunk(ChunkCoordinate c) {
for (ObjectOpenHashSet<ChunkCoordinate> h: needsAsyncPop) {
h.add(c);
}
}

public static synchronized void asyncPopulate() {
for (CompletableFuture<AsyncPopulationReturn> 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<BlockCoordinate> 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()) && 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());
ChunkSnapshot snapshot = currentChunk.getChunkSnapshot(true, true, false);
workingAsyncPopulators.add(attachedAsyncPopulators.get(i).populate(world, chunkRandom, snapshot, i));
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.polydev.gaea.population;

import java.util.HashSet;
import java.util.UUID;

public class AsyncPopulationReturn {
private final HashSet<BlockCoordinate> changeList;
private final int populatorId;
private final UUID worldID;
private final int chunkX;
private final int chunkZ;

public AsyncPopulationReturn(HashSet<BlockCoordinate> 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<BlockCoordinate> getChangeList() {
return changeList;
}

public int getPopulatorId() {
return populatorId;
}

public UUID getWorldID() {
return worldID;
}

public int getChunkX() {
return chunkX;
}

public int getChunkZ() {
return chunkZ;
}
}
38 changes: 38 additions & 0 deletions src/main/java/org/polydev/gaea/population/BlockCoordinate.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
16 changes: 9 additions & 7 deletions src/main/java/org/polydev/gaea/population/PopulationManager.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
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.generator.BlockPopulator;
Expand All @@ -14,13 +16,11 @@

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.*;

public class PopulationManager extends BlockPopulator {
private final List<GaeaBlockPopulator> attachedPopulators = new GlueList<>();
private final HashSet<ChunkCoordinate> needsPop = new HashSet<>();
private final List<GaeaBlockPopulator> attachedPopulators = new GlueList<GaeaBlockPopulator>();
private final ObjectOpenHashSet<ChunkCoordinate> needsPop = new ObjectOpenHashSet<>();
private final JavaPlugin main;
private final Object popLock = new Object();
private WorldProfiler profiler;
Expand All @@ -36,7 +36,8 @@ public void attach(GaeaBlockPopulator populator) {
@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);
int x = chunk.getX();
int z = chunk.getZ();
if(main.isEnabled()) {
Expand All @@ -63,7 +64,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<ChunkCoordinate>) needsPop.clone(), f);
SerializationUtil.toFile((ObjectOpenHashSet<ChunkCoordinate>) needsPop.clone(), f);
}

@SuppressWarnings("unchecked")
Expand All @@ -89,6 +90,7 @@ public synchronized void checkNeighbors(int x, int z, World w) {
r.populate(w, random, currentChunk);
}
needsPop.remove(c);
AsyncPopulationManager.addChunk(c);
}
}
}