Merge pull request #1071 from VolmitDev/fix_nullpointer

Fix nullpointer
This commit is contained in:
Brian Fopiano
2024-02-17 09:54:40 -05:00
committed by GitHub
@@ -1,413 +1,432 @@
/* /*
* Iris is a World Generator for Minecraft Bukkit Servers * Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software) * Copyright (c) 2022 Arcane Arts (Volmit Software)
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.volmit.iris.engine.platform; package com.volmit.iris.engine.platform;
import com.volmit.iris.Iris; import com.volmit.iris.Iris;
import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.loader.IrisData;
import com.volmit.iris.core.nms.INMS; import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.core.service.StudioSVC;
import com.volmit.iris.engine.IrisEngine; import com.volmit.iris.engine.IrisEngine;
import com.volmit.iris.engine.data.chunk.TerrainChunk; import com.volmit.iris.engine.data.chunk.TerrainChunk;
import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.framework.EngineTarget; import com.volmit.iris.engine.framework.EngineTarget;
import com.volmit.iris.engine.object.IrisDimension; import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.engine.object.IrisWorld; import com.volmit.iris.engine.object.IrisWorld;
import com.volmit.iris.engine.object.StudioMode; import com.volmit.iris.engine.object.StudioMode;
import com.volmit.iris.engine.platform.studio.StudioGenerator; import com.volmit.iris.engine.platform.studio.StudioGenerator;
import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.data.IrisBiomeStorage; import com.volmit.iris.util.data.IrisBiomeStorage;
import com.volmit.iris.util.hunk.Hunk; import com.volmit.iris.util.hunk.Hunk;
import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder; import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder;
import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder; import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder;
import com.volmit.iris.util.io.ReactiveFolder; import com.volmit.iris.util.io.ReactiveFolder;
import com.volmit.iris.util.scheduling.ChronoLatch; import com.volmit.iris.util.scheduling.ChronoLatch;
import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.J;
import com.volmit.iris.util.scheduling.Looper; import com.volmit.iris.util.scheduling.Looper;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Setter; import lombok.Setter;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldInitEvent; import org.bukkit.event.world.WorldInitEvent;
import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.BlockPopulator; import org.bukkit.generator.BlockPopulator;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.generator.WorldInfo; import org.bukkit.generator.WorldInfo;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer; import java.util.function.Consumer;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChunkGenerator, Listener { public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChunkGenerator, Listener {
private static final int LOAD_LOCKS = Runtime.getRuntime().availableProcessors() * 4; private static final int LOAD_LOCKS = Runtime.getRuntime().availableProcessors() * 4;
private final Semaphore loadLock; private final Semaphore loadLock;
private final IrisWorld world; private final IrisWorld world;
private final File dataLocation; private final File dataLocation;
private final String dimensionKey; private final String dimensionKey;
private final ReactiveFolder folder; private final ReactiveFolder folder;
private final ReentrantLock lock = new ReentrantLock(); private final ReentrantLock lock = new ReentrantLock();
private final KList<BlockPopulator> populators; private final KList<BlockPopulator> populators;
private final ChronoLatch hotloadChecker; private final ChronoLatch hotloadChecker;
private final AtomicBoolean setup; private final AtomicBoolean setup;
private final boolean studio; private final boolean studio;
private final AtomicInteger a = new AtomicInteger(0); private final AtomicInteger a = new AtomicInteger(0);
private Engine engine; private Engine engine;
private Looper hotloader; private Looper hotloader;
private StudioMode lastMode; private StudioMode lastMode;
private DummyBiomeProvider dummyBiomeProvider; private DummyBiomeProvider dummyBiomeProvider;
@Setter @Setter
private StudioGenerator studioGenerator; private StudioGenerator studioGenerator;
private boolean initialized = false; private boolean initialized = false;
public BukkitChunkGenerator(IrisWorld world, boolean studio, File dataLocation, String dimensionKey) { public BukkitChunkGenerator(IrisWorld world, boolean studio, File dataLocation, String dimensionKey) {
setup = new AtomicBoolean(false); setup = new AtomicBoolean(false);
studioGenerator = null; studioGenerator = null;
dummyBiomeProvider = new DummyBiomeProvider(); dummyBiomeProvider = new DummyBiomeProvider();
populators = new KList<>(); populators = new KList<>();
loadLock = new Semaphore(LOAD_LOCKS); loadLock = new Semaphore(LOAD_LOCKS);
this.world = world; this.world = world;
this.hotloadChecker = new ChronoLatch(1000, false); this.hotloadChecker = new ChronoLatch(1000, false);
this.studio = studio; this.studio = studio;
this.dataLocation = dataLocation; this.dataLocation = dataLocation;
this.dimensionKey = dimensionKey; this.dimensionKey = dimensionKey;
this.folder = new ReactiveFolder(dataLocation, (_a, _b, _c) -> hotload()); this.folder = new ReactiveFolder(dataLocation, (_a, _b, _c) -> hotload());
Bukkit.getServer().getPluginManager().registerEvents(this, Iris.instance); Bukkit.getServer().getPluginManager().registerEvents(this, Iris.instance);
} }
private static Field getField(Class clazz, String fieldName) private static Field getField(Class clazz, String fieldName)
throws NoSuchFieldException { throws NoSuchFieldException {
try { try {
return clazz.getDeclaredField(fieldName); return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) { } catch (NoSuchFieldException e) {
Class superClass = clazz.getSuperclass(); Class superClass = clazz.getSuperclass();
if (superClass == null) { if (superClass == null) {
throw e; throw e;
} else { } else {
return getField(superClass, fieldName); return getField(superClass, fieldName);
} }
} }
} }
@EventHandler @EventHandler
public void onWorldInit(WorldInitEvent event) { public void onWorldInit(WorldInitEvent event) {
try { try {
if (!initialized) { if (!initialized) {
world.setRawWorldSeed(event.getWorld().getSeed()); world.setRawWorldSeed(event.getWorld().getSeed());
if (world.name().equals(event.getWorld().getName())) { if (world.name().equals(event.getWorld().getName())) {
INMS.get().inject(event.getWorld().getSeed(), getEngine(event.getWorld()), event.getWorld()); Engine engine = getEngine(event.getWorld());
Iris.info("Injected Iris Biome Source into " + event.getWorld().getName()); if (engine == null) {
initialized = true; Iris.warn("Failed to get Engine!");
} J.s(() -> {
} Engine engine1 = getEngine(event.getWorld());
} catch (Throwable e) { if (engine1 != null) {
e.printStackTrace(); try {
} INMS.get().inject(event.getWorld().getSeed(), engine1, event.getWorld());
} Iris.info("Injected Iris Biome Source into " + event.getWorld().getName());
initialized = true;
private void setupEngine() { } catch (Throwable e) {
IrisData data = IrisData.get(dataLocation); e.printStackTrace();
IrisDimension dimension = data.getDimensionLoader().load(dimensionKey); }
}
if (dimension == null) { }, 10);
Iris.error("Oh No! There's no pack in " + data.getDataFolder().getPath() + " or... there's no dimension for the key " + dimensionKey); } else {
IrisDimension test = IrisData.loadAnyDimension(dimensionKey); INMS.get().inject(event.getWorld().getSeed(), engine, event.getWorld());
Iris.info("Injected Iris Biome Source into " + event.getWorld().getName());
if (test != null) { initialized = true;
Iris.warn("Looks like " + dimensionKey + " exists in " + test.getLoadFile().getPath() + " "); }
Iris.service(StudioSVC.class).installIntoWorld(Iris.getSender(), dimensionKey, dataLocation.getParentFile().getParentFile()); }
Iris.warn("Attempted to install into " + data.getDataFolder().getPath()); }
data.dump(); } catch (Throwable e) {
data.clearLists(); e.printStackTrace();
test = data.getDimensionLoader().load(dimensionKey); }
}
if (test != null) {
Iris.success("Woo! Patched the Engine!"); private void setupEngine() {
dimension = test; IrisData data = IrisData.get(dataLocation);
} else { IrisDimension dimension = data.getDimensionLoader().load(dimensionKey);
Iris.error("Failed to patch dimension!");
throw new RuntimeException("Missing Dimension: " + dimensionKey); if (dimension == null) {
} Iris.error("Oh No! There's no pack in " + data.getDataFolder().getPath() + " or... there's no dimension for the key " + dimensionKey);
} else { IrisDimension test = IrisData.loadAnyDimension(dimensionKey);
Iris.error("Nope, you don't have an installation containing " + dimensionKey + " try downloading it?");
throw new RuntimeException("Missing Dimension: " + dimensionKey); if (test != null) {
} Iris.warn("Looks like " + dimensionKey + " exists in " + test.getLoadFile().getPath() + " ");
} Iris.service(StudioSVC.class).installIntoWorld(Iris.getSender(), dimensionKey, dataLocation.getParentFile().getParentFile());
Iris.warn("Attempted to install into " + data.getDataFolder().getPath());
lastMode = StudioMode.NORMAL; data.dump();
engine = new IrisEngine(new EngineTarget(world, dimension, data), studio); data.clearLists();
populators.clear(); test = data.getDimensionLoader().load(dimensionKey);
}
if (test != null) {
@Override Iris.success("Woo! Patched the Engine!");
public void injectChunkReplacement(World world, int x, int z, Consumer<Runnable> jobs) { dimension = test;
try { } else {
loadLock.acquire(); Iris.error("Failed to patch dimension!");
IrisBiomeStorage st = new IrisBiomeStorage(); throw new RuntimeException("Missing Dimension: " + dimensionKey);
TerrainChunk tc = TerrainChunk.createUnsafe(world, st); }
Hunk<BlockData> blocks = Hunk.view(tc); } else {
Hunk<Biome> biomes = Hunk.view(tc, tc.getMinHeight(), tc.getMaxHeight()); Iris.error("Nope, you don't have an installation containing " + dimensionKey + " try downloading it?");
this.world.bind(world); throw new RuntimeException("Missing Dimension: " + dimensionKey);
getEngine().generate(x << 4, z << 4, blocks, biomes, true); }
Iris.debug("Regenerated " + x + " " + z); }
int t = 0;
for (int i = getEngine().getHeight() >> 4; i >= 0; i--) { lastMode = StudioMode.NORMAL;
if (!world.isChunkLoaded(x, z)) { engine = new IrisEngine(new EngineTarget(world, dimension, data), studio);
continue; populators.clear();
} }
Chunk c = world.getChunkAt(x, z); @Override
for (Entity ee : c.getEntities()) { public void injectChunkReplacement(World world, int x, int z, Consumer<Runnable> jobs) {
if (ee instanceof Player) { try {
continue; loadLock.acquire();
} IrisBiomeStorage st = new IrisBiomeStorage();
TerrainChunk tc = TerrainChunk.createUnsafe(world, st);
J.s(ee::remove); Hunk<BlockData> blocks = Hunk.view(tc);
} Hunk<Biome> biomes = Hunk.view(tc, tc.getMinHeight(), tc.getMaxHeight());
this.world.bind(world);
J.s(() -> engine.getWorldManager().onChunkLoad(c, false)); getEngine().generate(x << 4, z << 4, blocks, biomes, true);
Iris.debug("Regenerated " + x + " " + z);
int finalI = i; int t = 0;
jobs.accept(() -> { for (int i = getEngine().getHeight() >> 4; i >= 0; i--) {
if (!world.isChunkLoaded(x, z)) {
for (int xx = 0; xx < 16; xx++) { continue;
for (int yy = 0; yy < 16; yy++) { }
for (int zz = 0; zz < 16; zz++) {
if (yy + (finalI << 4) >= engine.getHeight() || yy + (finalI << 4) < 0) { Chunk c = world.getChunkAt(x, z);
continue; for (Entity ee : c.getEntities()) {
} if (ee instanceof Player) {
c.getBlock(xx, yy + (finalI << 4) + world.getMinHeight(), zz) continue;
.setBlockData(tc.getBlockData(xx, yy + (finalI << 4) + world.getMinHeight(), zz), false); }
}
} J.s(ee::remove);
} }
});
} J.s(() -> engine.getWorldManager().onChunkLoad(c, false));
loadLock.release(); int finalI = i;
} catch (Throwable e) { jobs.accept(() -> {
loadLock.release();
Iris.error("======================================"); for (int xx = 0; xx < 16; xx++) {
e.printStackTrace(); for (int yy = 0; yy < 16; yy++) {
Iris.reportErrorChunk(x, z, e, "CHUNK"); for (int zz = 0; zz < 16; zz++) {
Iris.error("======================================"); if (yy + (finalI << 4) >= engine.getHeight() || yy + (finalI << 4) < 0) {
continue;
ChunkData d = Bukkit.createChunkData(world); }
c.getBlock(xx, yy + (finalI << 4) + world.getMinHeight(), zz)
for (int i = 0; i < 16; i++) { .setBlockData(tc.getBlockData(xx, yy + (finalI << 4) + world.getMinHeight(), zz), false);
for (int j = 0; j < 16; j++) { }
d.setBlock(i, 0, j, Material.RED_GLAZED_TERRACOTTA.createBlockData()); }
} }
} });
} }
}
loadLock.release();
private Engine getEngine(WorldInfo world) { } catch (Throwable e) {
if (setup.get()) { loadLock.release();
return getEngine(); Iris.error("======================================");
} e.printStackTrace();
Iris.reportErrorChunk(x, z, e, "CHUNK");
lock.lock(); Iris.error("======================================");
if (setup.get()) { ChunkData d = Bukkit.createChunkData(world);
return getEngine();
} for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
d.setBlock(i, 0, j, Material.RED_GLAZED_TERRACOTTA.createBlockData());
setup.set(true); }
getWorld().setRawWorldSeed(world.getSeed()); }
setupEngine(); }
this.hotloader = studio ? new Looper() { }
@Override
protected long loop() { private Engine getEngine(WorldInfo world) {
if (hotloadChecker.flip()) { if (setup.get()) {
folder.check(); return getEngine();
} }
return 250; lock.lock();
}
} : null; try {
if (setup.get()) {
if (studio) { return getEngine();
hotloader.setPriority(Thread.MIN_PRIORITY); }
hotloader.start();
hotloader.setName(getTarget().getWorld().name() + " Hotloader");
} getWorld().setRawWorldSeed(world.getSeed());
setupEngine();
lock.unlock(); setup.set(true);
this.hotloader = studio ? new Looper() {
return engine; @Override
} protected long loop() {
if (hotloadChecker.flip()) {
@Override folder.check();
public void close() { }
withExclusiveControl(() -> {
if (isStudio()) { return 250;
hotloader.interrupt(); }
} } : null;
getEngine().close(); if (studio) {
folder.clear(); hotloader.setPriority(Thread.MIN_PRIORITY);
populators.clear(); hotloader.start();
hotloader.setName(getTarget().getWorld().name() + " Hotloader");
}); }
}
return engine;
@Override } finally {
public boolean isStudio() { lock.unlock();
return studio; }
} }
@Override @Override
public void hotload() { public void close() {
if (!isStudio()) { withExclusiveControl(() -> {
return; if (isStudio()) {
} hotloader.interrupt();
}
withExclusiveControl(() -> getEngine().hotload());
} getEngine().close();
folder.clear();
public void withExclusiveControl(Runnable r) { populators.clear();
J.a(() -> {
try { });
loadLock.acquire(LOAD_LOCKS); }
r.run();
loadLock.release(LOAD_LOCKS); @Override
} catch (Throwable e) { public boolean isStudio() {
Iris.reportError(e); return studio;
} }
});
} @Override
public void hotload() {
@Override if (!isStudio()) {
public void touch(World world) { return;
getEngine(world); }
}
withExclusiveControl(() -> getEngine().hotload());
@Override }
public void generateNoise(@NotNull WorldInfo world, @NotNull Random random, int x, int z, @NotNull ChunkGenerator.ChunkData d) {
try { public void withExclusiveControl(Runnable r) {
getEngine(world); J.a(() -> {
computeStudioGenerator(); try {
TerrainChunk tc = TerrainChunk.create(d, new IrisBiomeStorage()); loadLock.acquire(LOAD_LOCKS);
this.world.bind(world); r.run();
if (studioGenerator != null) { loadLock.release(LOAD_LOCKS);
studioGenerator.generateChunk(getEngine(), tc, x, z); } catch (Throwable e) {
} else { Iris.reportError(e);
ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(tc); }
BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(tc, tc.getMinHeight(), tc.getMaxHeight()); });
getEngine().generate(x << 4, z << 4, blocks, biomes, false); }
blocks.apply();
biomes.apply(); @Override
} public void touch(World world) {
getEngine(world);
Iris.debug("Generated " + x + " " + z); }
} catch (Throwable e) {
Iris.error("======================================"); @Override
e.printStackTrace(); public void generateNoise(@NotNull WorldInfo world, @NotNull Random random, int x, int z, @NotNull ChunkGenerator.ChunkData d) {
Iris.reportErrorChunk(x, z, e, "CHUNK"); try {
Iris.error("======================================"); getEngine(world);
computeStudioGenerator();
for (int i = 0; i < 16; i++) { TerrainChunk tc = TerrainChunk.create(d, new IrisBiomeStorage());
for (int j = 0; j < 16; j++) { this.world.bind(world);
d.setBlock(i, 0, j, Material.RED_GLAZED_TERRACOTTA.createBlockData()); if (studioGenerator != null) {
} studioGenerator.generateChunk(getEngine(), tc, x, z);
} } else {
} ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(tc);
} BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(tc, tc.getMinHeight(), tc.getMaxHeight());
getEngine().generate(x << 4, z << 4, blocks, biomes, false);
@Override blocks.apply();
public int getBaseHeight(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull HeightMap heightMap) { biomes.apply();
return 4; }
}
Iris.debug("Generated " + x + " " + z);
private void computeStudioGenerator() { } catch (Throwable e) {
if (!getEngine().getDimension().getStudioMode().equals(lastMode)) { Iris.error("======================================");
lastMode = getEngine().getDimension().getStudioMode(); e.printStackTrace();
getEngine().getDimension().getStudioMode().inject(this); Iris.reportErrorChunk(x, z, e, "CHUNK");
} Iris.error("======================================");
}
for (int i = 0; i < 16; i++) {
@NotNull for (int j = 0; j < 16; j++) {
@Override d.setBlock(i, 0, j, Material.RED_GLAZED_TERRACOTTA.createBlockData());
public List<BlockPopulator> getDefaultPopulators(@NotNull World world) { }
return populators; }
} }
}
@Override
public boolean isParallelCapable() { @Override
return true; public int getBaseHeight(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull HeightMap heightMap) {
} return 4;
}
@Override
public boolean shouldGenerateCaves() { private void computeStudioGenerator() {
return false; if (!getEngine().getDimension().getStudioMode().equals(lastMode)) {
} lastMode = getEngine().getDimension().getStudioMode();
getEngine().getDimension().getStudioMode().inject(this);
@Override }
public boolean shouldGenerateDecorations() { }
return false;
} @NotNull
@Override
@Override public List<BlockPopulator> getDefaultPopulators(@NotNull World world) {
public boolean shouldGenerateMobs() { return populators;
return false; }
}
@Override
@Override public boolean isParallelCapable() {
public boolean shouldGenerateStructures() { return true;
return false; }
}
@Override
@Override public boolean shouldGenerateCaves() {
public boolean shouldGenerateNoise() { return false;
return false; }
}
@Override
@Override public boolean shouldGenerateDecorations() {
public boolean shouldGenerateSurface() { return false;
return false; }
}
@Override
@Override public boolean shouldGenerateMobs() {
public boolean shouldGenerateBedrock() { return false;
return false; }
}
@Override
@Nullable public boolean shouldGenerateStructures() {
@Override return false;
public BiomeProvider getDefaultBiomeProvider(@NotNull WorldInfo worldInfo) { }
return dummyBiomeProvider;
} @Override
} public boolean shouldGenerateNoise() {
return false;
}
@Override
public boolean shouldGenerateSurface() {
return false;
}
@Override
public boolean shouldGenerateBedrock() {
return false;
}
@Nullable
@Override
public BiomeProvider getDefaultBiomeProvider(@NotNull WorldInfo worldInfo) {
return dummyBiomeProvider;
}
}