From b782f5ced9b842edbffd817b704587ec5eb5cdfc Mon Sep 17 00:00:00 2001 From: samczsun Date: Sun, 19 Feb 2017 23:53:19 -0500 Subject: [PATCH] Speed up generation --- .../nautilus/game/arcade/uhc/WorldGen.java | 465 +++++++++--------- .../game/arcade/game/games/uhc/UHC.java | 12 +- 2 files changed, 248 insertions(+), 229 deletions(-) diff --git a/Plugins/Nautilus.Game.Arcade.UHC.WorldGen/src/nautilus/game/arcade/uhc/WorldGen.java b/Plugins/Nautilus.Game.Arcade.UHC.WorldGen/src/nautilus/game/arcade/uhc/WorldGen.java index c2e322e7f..812b1f45a 100644 --- a/Plugins/Nautilus.Game.Arcade.UHC.WorldGen/src/nautilus/game/arcade/uhc/WorldGen.java +++ b/Plugins/Nautilus.Game.Arcade.UHC.WorldGen/src/nautilus/game/arcade/uhc/WorldGen.java @@ -1,10 +1,7 @@ package nautilus.game.arcade.uhc; -import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; @@ -13,7 +10,6 @@ import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.logging.Level; -import java.util.zip.ZipEntry; import net.minecraft.server.v1_8_R3.BiomeBase; @@ -27,26 +23,35 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicHeader; import org.apache.http.protocol.HTTP; import org.bukkit.Bukkit; +import org.bukkit.Chunk; import org.bukkit.Difficulty; import org.bukkit.World; +import org.bukkit.WorldBorder; import org.bukkit.WorldCreator; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; +import org.bukkit.entity.Entity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.world.ChunkUnloadEvent; +import org.bukkit.event.world.WorldInitEvent; import org.bukkit.plugin.java.JavaPlugin; import org.spigotmc.WatchdogThread; +import org.zeroturnaround.zip.ByteSource; +import org.zeroturnaround.zip.FileSource; import org.zeroturnaround.zip.ZipEntrySource; import org.zeroturnaround.zip.ZipUtil; import com.google.gson.Gson; import com.google.gson.JsonObject; -public class WorldGen extends JavaPlugin +public class WorldGen extends JavaPlugin implements Listener { private static final int TIMEOUT = (int) TimeUnit.SECONDS.toMillis(10); - private static final int MIN_X = -1000; - private static final int MIN_Z = -1000; - private static final int MAX_X = 1000; - private static final int MAX_Z = 1000; + // The world will be -MAP_SIZE to MAP_SIZE large + private static final int MAP_SIZE = 1000; private static final int VIEW_DISTANCE = 5; private static final String API_HOST_FILE = "api-config.dat"; @@ -78,264 +83,276 @@ public class WorldGen extends JavaPlugin } } + @EventHandler + public void login(AsyncPlayerPreLoginEvent event) + { + event.setKickMessage("get out"); + event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); + } + + @EventHandler + public void unload(ChunkUnloadEvent event) + { + event.setCancelled(true); + } + + @EventHandler + public void init(WorldInitEvent event) + { + // Prevent any eager generation + event.getWorld().setKeepSpawnInMemory(false); + } + @Override public void onEnable() { + getLogger().info("Cleaning up other worlds"); + for (World world : getServer().getWorlds()) + { + world.setKeepSpawnInMemory(false); + world.setSpawnFlags(false, false); + world.setAmbientSpawnLimit(0); + world.setAnimalSpawnLimit(0); + world.setMonsterSpawnLimit(0); + world.setWaterAnimalSpawnLimit(0); + world.getEntities().forEach(Entity::remove); + for (Chunk chunk : world.getLoadedChunks()) + { + chunk.unload(false, false); + } + getServer().unloadWorld(world, false); + getLogger().info("Unloaded " + world.getName()); + } + + getLogger().info("Replacing biomes"); BiomeBase.getBiomes()[BiomeBase.OCEAN.id] = BiomeBase.PLAINS; BiomeBase.getBiomes()[BiomeBase.DEEP_OCEAN.id] = BiomeBase.PLAINS; BiomeBase.getBiomes()[BiomeBase.SWAMPLAND.id] = BiomeBase.PLAINS; BiomeBase.getBiomes()[BiomeBase.RIVER.id] = BiomeBase.PLAINS; + getLogger().info("Forcing system GC"); + System.gc(); + WatchdogThread.doStop(); + getServer().getPluginManager().registerEvents(this, this); + + File root = new File("."); + + if (!root.exists()) + { + getLogger().severe("Root folder does not exist. Aborting"); + System.exit(0); + return; + } + + File outputDirectory = new File(root, "output"); + if (!outputDirectory.exists()) + { + if (!outputDirectory.mkdir()) + { + getLogger().severe("Could not create output folder. Aborting"); + System.exit(0); + return; + } + } + + long seed = ThreadLocalRandom.current().nextLong(); + + File outputFile = new File(outputDirectory, "UHC_Map" + seed + ".zip"); + + if (outputFile.exists()) + { + getLogger().info("Seed " + seed + " has already been generated. Skipping"); + System.exit(0); + return; + } try { - File root = new File("."); - - if (!root.exists()) + if (!outputFile.createNewFile()) { - getLogger().severe("Root folder does not exist. Aborting"); + getLogger().severe("Could not create new output file. Aborting"); System.exit(0); return; } + } + catch (IOException e) + { + getLogger().log(Level.SEVERE, "Could not create new output file. Aborting", e); + System.exit(0); + return; + } - File outputDirectory = new File(root, "output"); - if (!outputDirectory.exists()) + File previousSession = new File("generating"); + + if (previousSession.exists()) + { + if (!FileUtils.deleteQuietly(previousSession)) { - if (!outputDirectory.mkdir()) - { - getLogger().severe("Could not create output folder. Aborting"); - System.exit(0); - return; - } - } - - long seed = ThreadLocalRandom.current().nextLong(); - - File outputFile = new File(outputDirectory, "UHC_Map" + seed + ".zip"); - - if (outputFile.exists()) - { - getLogger().info("Seed " + seed + " has already been generated. Skipping"); + getLogger().severe("Could not delete previous generation session. Aborting"); System.exit(0); return; } + } - try + getLogger().info("Generating world seed " + seed); + + World world = new WorldCreator("generating") + .environment(World.Environment.NORMAL) + .seed(seed) + .createWorld(); + world.setKeepSpawnInMemory(false); + world.setDifficulty(Difficulty.HARD); + WorldBorder border = world.getWorldBorder(); + border.setCenter(0.0, 0.0); + border.setSize(MAP_SIZE * 2); + + int minChunkX = (-MAP_SIZE >> 4) - VIEW_DISTANCE; + int minChunkZ = (-MAP_SIZE >> 4) - VIEW_DISTANCE; + int maxChunkX = (MAP_SIZE >> 4) + VIEW_DISTANCE; + int maxChunkZ = (MAP_SIZE >> 4) + VIEW_DISTANCE; + + net.minecraft.server.v1_8_R3.WorldServer nmsWorld = ((CraftWorld) world).getHandle(); +// +// Field mfield = nmsWorld.getClass().getDeclaredField("M"); +// mfield.setAccessible(true); +// +// HashTreeSet treeSet = ((HashTreeSet) mfield.get(nmsWorld)); + + for (int x = minChunkX; x <= maxChunkX; x++) + { + getLogger().info("Generating x coord " + x); + for (int z = minChunkZ; z <= maxChunkZ; z++) { - if (!outputFile.createNewFile()) - { - getLogger().severe("Could not create new output file. Aborting"); - System.exit(0); - return; - } - } - catch (IOException e) - { - getLogger().log(Level.SEVERE, "Could not create new output file. Aborting", e); - System.exit(0); - return; + world.getChunkAt(x, z).load(true); + nmsWorld.a(true); + // Manually tick blocks - this should be the equivalent of letting a full server tick run once + // between each chunk generation, except we cut out the extra useless stuff } - getLogger().info("Generating world seed " + seed); +// System.out.println("M: " + treeSet.size()); +// System.out.println("E: " + nmsWorld.entityList.size()); +// System.out.println("TE: " + nmsWorld.tileEntityList.size()); +// System.out.println("C: " + nmsWorld.chunkProviderServer.chunks.size()); + } - World world = new WorldCreator("generating") - .environment(World.Environment.NORMAL) - .seed(seed) - .createWorld(); - world.setDifficulty(Difficulty.HARD); - world.setKeepSpawnInMemory(false); - - int minChunkX = (MIN_X >> 4) - VIEW_DISTANCE; - int minChunkZ = (MIN_Z >> 4) - VIEW_DISTANCE; - int maxChunkX = (MAX_X >> 4) + VIEW_DISTANCE; - int maxChunkZ = (MAX_Z >> 4) + VIEW_DISTANCE; - - for (int x = minChunkX; x <= maxChunkX; x++) + for (int x = minChunkX; x <= maxChunkX; x++) + { + getLogger().info("Unloading x coord " + x); + for (int z = minChunkZ; z <= maxChunkZ; z++) { - getLogger().info("Generating x coord " + x); - for (int z = minChunkZ; z <= maxChunkZ; z++) - { - world.getChunkAt(x, z).load(true); - } + world.getChunkAt(x, z).unload(true, false); } - for (int x = minChunkX; x <= maxChunkX; x++) +// System.out.println("M: " + treeSet.size()); +// System.out.println("E: " + nmsWorld.entityList.size()); +// System.out.println("TE: " + nmsWorld.tileEntityList.size()); +// System.out.println("C: " + nmsWorld.chunkProviderServer.chunks.size()); + } + + getLogger().info("Unloading and saving world"); + + Bukkit.unloadWorld(world, true); + + getLogger().info("Finished unloading and saving world"); + + StringBuilder worldconfig = new StringBuilder(); + worldconfig.append("MAP_NAME:UHC World").append(System.lineSeparator()); + worldconfig.append("MAP_AUTHOR:Mineplex").append(System.lineSeparator()); + worldconfig.append("MIN_X:").append(-MAP_SIZE).append(System.lineSeparator()); + worldconfig.append("MIN_Z:").append(-MAP_SIZE).append(System.lineSeparator()); + worldconfig.append("MAX_X:").append(MAP_SIZE).append(System.lineSeparator()); + worldconfig.append("MAX_Z:").append(MAP_SIZE).append(System.lineSeparator()); + for (int i = 1; i <= 60; i++) + { + worldconfig.append("TEAM_NAME:").append(i).append(System.lineSeparator()); + worldconfig.append("TEAM_SPAWNS:0,0,0").append(System.lineSeparator()); + } + + File worldFolder = new File(root, "generating"); + + File regionFolder = new File(worldFolder, "region"); + + File[] regionFiles = regionFolder.listFiles(); + + if (regionFiles == null) + { + getLogger().severe("Unexpected null region files. Aborting"); + System.exit(0); + return; + } + + List zipEntrySourceList = new ArrayList<>(); + zipEntrySourceList.add(new ByteSource("WorldConfig.dat", worldconfig.toString().getBytes(StandardCharsets.UTF_8))); + for (File file : regionFiles) + { + zipEntrySourceList.add(new FileSource("region/" + file.getName(), file)); + } + zipEntrySourceList.add(new FileSource("level.dat", new File(worldFolder, "level.dat"))); + + ZipUtil.pack(zipEntrySourceList.toArray(new ZipEntrySource[zipEntrySourceList.size()]), outputFile); + + FileUtils.deleteQuietly(worldFolder); + + RequestConfig config = RequestConfig.custom() + .setConnectTimeout(TIMEOUT) + .setSocketTimeout(TIMEOUT) + .build(); + + CloseableHttpClient httpClient = HttpClientBuilder.create() + .setDefaultRequestConfig(config) + .build(); + + HttpPost request = new HttpPost("http://" + API_HOST_MAP.get("ENDERCHEST") + "/map/uhc/upload"); + request.addHeader(new BasicHeader(HTTP.CONTENT_TYPE, "application/json")); + + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("name", outputFile.getName()); + jsonObject.addProperty("location", outputFile.toURI().toString()); + + request.setEntity(new StringEntity(new Gson().toJson(jsonObject), StandardCharsets.UTF_8)); + + try + { + getLogger().info("Uploading " + seed + "!"); + HttpResponse response = httpClient.execute(request); + + if (response.getStatusLine().getStatusCode() != 200) { - getLogger().info("Unloading x coord " + x); - for (int z = minChunkZ; z <= maxChunkZ; z++) + if (response.getStatusLine().getStatusCode() == 409) { - world.getChunkAt(x, z).unload(true, false); - } - } - - getLogger().info("Unloading and saving world"); - - Bukkit.unloadWorld(world, true); - - getLogger().info("Finished unloading and saving world"); - - StringBuilder worldconfig = new StringBuilder(); - worldconfig.append("MAP_NAME:UHC World").append(System.lineSeparator()); - worldconfig.append("MAP_AUTHOR:Mineplex").append(System.lineSeparator()); - worldconfig.append("MIN_X:").append(MIN_X).append(System.lineSeparator()); - worldconfig.append("MIN_Z:").append(MIN_Z).append(System.lineSeparator()); - worldconfig.append("MAX_X:").append(MAX_X).append(System.lineSeparator()); - worldconfig.append("MAX_Z:").append(MAX_Z).append(System.lineSeparator()); - for (int i = 1; i <= 60; i++) - { - worldconfig.append("TEAM_NAME:").append(i).append(System.lineSeparator()); - worldconfig.append("TEAM_SPAWNS:0,0,0").append(System.lineSeparator()); - } - - File worldFolder = new File(root, "generating"); - - File regionFolder = new File(worldFolder, "region"); - - File[] regionFiles = regionFolder.listFiles(); - - if (regionFiles == null) - { - getLogger().severe("Unexpected null region files. Aborting"); - System.exit(0); - return; - } - - List zipEntrySourceList = new ArrayList<>(); - zipEntrySourceList.add(new ZipEntrySource() - { - @Override - public String getPath() - { - return "WorldConfig.dat"; - } - - @Override - public ZipEntry getEntry() - { - return new ZipEntry(getPath()); - } - - @Override - public InputStream getInputStream() throws IOException - { - return new ByteArrayInputStream(worldconfig.toString().getBytes(StandardCharsets.UTF_8)); - } - }); - - - for (File file : regionFiles) - { - zipEntrySourceList.add(new ZipEntrySource() - { - @Override - public String getPath() - { - return "region/" + file.getName(); - } - - @Override - public ZipEntry getEntry() - { - return new ZipEntry(getPath()); - } - - @Override - public InputStream getInputStream() throws IOException - { - return new FileInputStream(file); - } - }); - } - - zipEntrySourceList.add(new ZipEntrySource() - { - @Override - public String getPath() - { - return "level.dat"; - } - - @Override - public ZipEntry getEntry() - { - return new ZipEntry(getPath()); - } - - @Override - public InputStream getInputStream() throws IOException - { - return new FileInputStream(new File(worldFolder, "level.dat")); - } - }); - - ZipUtil.pack(zipEntrySourceList.toArray(new ZipEntrySource[zipEntrySourceList.size()]), outputFile); - - FileUtils.deleteQuietly(worldFolder); - - RequestConfig config = RequestConfig.custom() - .setConnectTimeout(TIMEOUT) - .setSocketTimeout(TIMEOUT) - .build(); - - CloseableHttpClient httpClient = HttpClientBuilder.create() - .setDefaultRequestConfig(config) - .build(); - - HttpPost request = new HttpPost("http://" + API_HOST_MAP.get("ENDERCHEST") + "/map/uhc/upload"); - request.addHeader(new BasicHeader(HTTP.CONTENT_TYPE, "application/json")); - - JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("name", outputFile.getName()); - jsonObject.addProperty("location", outputFile.toURI().toString()); - - request.setEntity(new StringEntity(new Gson().toJson(jsonObject), StandardCharsets.UTF_8)); - - try - { - getLogger().info("Uploading " + seed + "!"); - HttpResponse response = httpClient.execute(request); - - if (response.getStatusLine().getStatusCode() != 200) - { - if (response.getStatusLine().getStatusCode() == 409) - { - getLogger().warning("Oops - Server rejected " + seed + " because it was already generated"); - - if (!outputFile.delete()) - { - getLogger().warning("Could not clean up " + seed); - } - } - else - { - getLogger().severe("Failed to upload " + seed + ": " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase()); - } - } - else - { - getLogger().info("Uploaded " + seed + "!"); + getLogger().warning("Oops - Server rejected " + seed + " because it was already generated"); if (!outputFile.delete()) { getLogger().warning("Could not clean up " + seed); } } + else + { + getLogger().severe("Failed to upload " + seed + ": " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase()); + } } - catch (IOException e) + else { - e.printStackTrace(); - } - finally - { - getLogger().info("Finished generating world seed " + seed); + getLogger().info("Uploaded " + seed + "!"); + + if (!outputFile.delete()) + { + getLogger().warning("Could not clean up " + seed); + } } } - catch (Throwable t) + catch (IOException e) { - t.printStackTrace(); + getLogger().log(Level.SEVERE, "An error occurred while uploading " + seed + "!", e); } - System.exit(0); + finally + { + getLogger().info("Finished generating world seed " + seed); + } + + Bukkit.shutdown(); } } diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/uhc/UHC.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/uhc/UHC.java index e79c8f261..3e5b66bc6 100644 --- a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/uhc/UHC.java +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/uhc/UHC.java @@ -365,17 +365,19 @@ public abstract class UHC extends Game UtilPlayer.message(caller, F.main("Debug", "World info for " + targetWorld.getName())); UtilPlayer.message(caller, F.desc("Chunks", String.valueOf(chunks.length))); UtilPlayer.message(caller, F.desc("Entities", String.valueOf(targetWorld.getEntities().size()))); - UtilPlayer.message(caller, F.desc("Tile Entities", String.valueOf(Arrays.stream(chunks).map(Chunk::getTileEntities).map(Arrays::asList).flatMap(Collection::stream) - .count()))); + UtilPlayer.message(caller, F.desc("Tile Entities", String.valueOf(Arrays.stream(chunks).map(Chunk::getTileEntities).map(Arrays::asList).mapToLong(Collection::size).sum()))); UtilPlayer.message(caller, F.desc("View Distance", String.valueOf(nmsWorld.spigotConfig.viewDistance))); UtilPlayer.message(caller, F.desc("Unload queue size", String.valueOf(nmsWorld.chunkProviderServer.unloadQueue.size()))); try { - HashTreeSet m = (HashTreeSet) nmsWorld.getClass().getField("M").get(nmsWorld); + Field f = nmsWorld.getClass().getDeclaredField("M"); + f.setAccessible(true); + HashTreeSet m = (HashTreeSet) f.get(nmsWorld); + UtilPlayer.message(caller, F.desc("Pending tick", String.valueOf(m.size()))); } - catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) + catch (ReflectiveOperationException e) { e.printStackTrace(); } @@ -571,7 +573,7 @@ public abstract class UHC extends Game } } }); - registerDebugCommand(new DebugCommand("uhccallchunks", Rank.DEVELOPER) + registerDebugCommand(new DebugCommand("uhcallchunks", Rank.DEVELOPER) { @Override