9
0
mirror of https://github.com/VolmitSoftware/Iris.git synced 2025-12-28 03:29:06 +00:00

Merge remote-tracking branch 'upstream/master' into DecreeCommands

This commit is contained in:
CocoTheOwner
2021-08-23 13:21:27 +02:00
92 changed files with 2823 additions and 1436 deletions

View File

@@ -22,7 +22,10 @@ import com.volmit.iris.Iris;
import com.volmit.iris.core.IrisSettings;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.collection.KSet;
import com.volmit.iris.util.scheduling.ChronoLatch;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.ints.IntSets;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
@@ -33,395 +36,201 @@ import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import static org.bukkit.Material.*;
public class B {
private static final Material AIR_MATERIAL = Material.AIR;
private static final BlockData AIR = AIR_MATERIAL.createBlockData();
private static final KSet<String> nullBlockDataCache = new KSet<>();
private static final KSet<String> nullMaterialCache = new KSet<>();
private static final KMap<Material, Boolean> solidCache = new KMap<>();
private static final KMap<Material, Boolean> updatableCache = new KMap<>();
private static final KMap<Material, Boolean> foliageCache = new KMap<>();
private static final KMap<Material, Boolean> litCache = new KMap<>();
private static final KMap<Material, Boolean> decorantCache = new KMap<>();
private static final KMap<Material, Boolean> storageCache = new KMap<>();
private static final KMap<Material, Boolean> storageChestCache = new KMap<>();
private static final IntSet foliageCache = buildFoliageCache();
private static final IntSet decorantCache = buildDecorantCache();
private static final IntSet storageCache = buildStorageCache();
private static final IntSet storageChestCache = buildStorageChestCache();
private static final IntSet litCache = buildLitCache();
private static final KMap<String, BlockData> blockDataCache = new KMap<>();
private static final KMap<String, Material> materialCache = new KMap<>();
private static final ChronoLatch clw = new ChronoLatch(1000);
public static boolean isWater(BlockData b) {
return b.getMaterial().equals(Material.WATER);
private static IntSet buildFoliageCache() {
IntSet b = new IntOpenHashSet();
Arrays.stream(new Material[]{
POPPY,
DANDELION,
CORNFLOWER,
SWEET_BERRY_BUSH,
CRIMSON_ROOTS,
WARPED_ROOTS,
NETHER_SPROUTS,
ALLIUM,
AZURE_BLUET,
BLUE_ORCHID,
OXEYE_DAISY,
LILY_OF_THE_VALLEY,
WITHER_ROSE,
DARK_OAK_SAPLING,
ACACIA_SAPLING,
JUNGLE_SAPLING,
BIRCH_SAPLING,
SPRUCE_SAPLING,
OAK_SAPLING,
ORANGE_TULIP,
PINK_TULIP,
RED_TULIP,
WHITE_TULIP,
FERN,
LARGE_FERN,
GRASS,
TALL_GRASS
}).forEach((i) -> b.add(i.ordinal()));
return IntSets.unmodifiable(b);
}
public static BlockData getAir() {
return AIR;
private static IntSet buildDecorantCache() {
IntSet b = new IntOpenHashSet();
Arrays.stream(new Material[]{
GRASS,
TALL_GRASS,
FERN,
LARGE_FERN,
CORNFLOWER,
SUNFLOWER,
CHORUS_FLOWER,
POPPY,
DANDELION,
OXEYE_DAISY,
ORANGE_TULIP,
PINK_TULIP,
RED_TULIP,
WHITE_TULIP,
LILAC,
DEAD_BUSH,
SWEET_BERRY_BUSH,
ROSE_BUSH,
WITHER_ROSE,
ALLIUM,
BLUE_ORCHID,
LILY_OF_THE_VALLEY,
CRIMSON_FUNGUS,
WARPED_FUNGUS,
RED_MUSHROOM,
BROWN_MUSHROOM,
CRIMSON_ROOTS,
AZURE_BLUET,
WEEPING_VINES,
WEEPING_VINES_PLANT,
WARPED_ROOTS,
NETHER_SPROUTS,
TWISTING_VINES,
TWISTING_VINES_PLANT,
SUGAR_CANE,
WHEAT,
POTATOES,
CARROTS,
BEETROOTS,
NETHER_WART,
SEA_PICKLE,
SEAGRASS,
ACACIA_BUTTON,
BIRCH_BUTTON,
CRIMSON_BUTTON,
DARK_OAK_BUTTON,
JUNGLE_BUTTON,
OAK_BUTTON,
POLISHED_BLACKSTONE_BUTTON,
SPRUCE_BUTTON,
STONE_BUTTON,
WARPED_BUTTON,
TORCH,
SOUL_TORCH
}).forEach((i) -> b.add(i.ordinal()));
b.addAll(foliageCache);
return IntSets.unmodifiable(b);
}
public static Material getMaterial(String bdx) {
Material mat = getMaterialOrNull(bdx);
private static IntSet buildLitCache() {
IntSet b = new IntOpenHashSet();
Arrays.stream(new Material[]{
GLOWSTONE,
END_ROD,
SOUL_SAND,
TORCH,
REDSTONE_TORCH,
SOUL_TORCH,
REDSTONE_WALL_TORCH,
WALL_TORCH,
SOUL_WALL_TORCH,
LANTERN,
CANDLE,
JACK_O_LANTERN,
REDSTONE_LAMP,
MAGMA_BLOCK,
LIGHT,
SHROOMLIGHT,
SEA_LANTERN,
SOUL_LANTERN,
FIRE,
SOUL_FIRE,
SEA_PICKLE,
BREWING_STAND,
REDSTONE_ORE,
}).forEach((i) -> b.add(i.ordinal()));
if (mat != null) {
return mat;
}
return AIR_MATERIAL;
return IntSets.unmodifiable(b);
}
public static Material getMaterialOrNull(String bdxx) {
String bx = bdxx.trim().toUpperCase();
private static IntSet buildStorageCache() {
IntSet b = new IntOpenHashSet();
Arrays.stream(new Material[]{
CHEST,
SMOKER,
TRAPPED_CHEST,
SHULKER_BOX,
WHITE_SHULKER_BOX,
ORANGE_SHULKER_BOX,
MAGENTA_SHULKER_BOX,
LIGHT_BLUE_SHULKER_BOX,
YELLOW_SHULKER_BOX,
LIME_SHULKER_BOX,
PINK_SHULKER_BOX,
GRAY_SHULKER_BOX,
LIGHT_GRAY_SHULKER_BOX,
CYAN_SHULKER_BOX,
PURPLE_SHULKER_BOX,
BLUE_SHULKER_BOX,
BROWN_SHULKER_BOX,
GREEN_SHULKER_BOX,
RED_SHULKER_BOX,
BLACK_SHULKER_BOX,
BARREL,
DISPENSER,
DROPPER,
HOPPER,
FURNACE,
BLAST_FURNACE
}).forEach((i) -> b.add(i.ordinal()));
if (nullMaterialCache.contains(bx)) {
return null;
}
Material mat = materialCache.get(bx);
if (mat != null) {
return mat;
}
try {
Material mm = Material.valueOf(bx);
materialCache.put(bx, mm);
return mm;
} catch (Throwable e) {
Iris.reportError(e);
nullMaterialCache.add(bx);
return null;
}
return IntSets.unmodifiable(b);
}
public static boolean isSolid(BlockData mat) {
return isSolid(mat.getMaterial());
}
private static IntSet buildStorageChestCache() {
IntSet b = new IntOpenHashSet(storageCache);
b.remove(SMOKER.ordinal());
b.remove(FURNACE.ordinal());
b.remove(BLAST_FURNACE.ordinal());
public static boolean isSolid(Material mat) {
Boolean solid = solidCache.get(mat);
if (solid != null) {
return solid;
}
solid = mat.isSolid();
solidCache.put(mat, solid);
return solid;
}
public static BlockData getOrNull(String bdxf) {
try {
String bd = bdxf.trim();
BlockData bdx = parseBlockData(bd);
if (bdx == null) {
Iris.warn("Unknown Block Data '" + bd + "'");
return AIR;
}
return bdx;
} catch (Throwable e) {
Iris.reportError(e);
Iris.warn("Unknown Block Data '" + bdxf + "'");
}
return null;
}
public static BlockData get(String bdxf) {
BlockData bd = getOrNull(bdxf);
if (bd != null) {
return bd;
}
return AIR;
}
private static BlockData parseBlockDataOrNull(String ix) {
if (nullBlockDataCache.contains(ix)) {
return null;
}
try {
BlockData bb = blockDataCache.get(ix);
if (bb != null) {
return bb;
}
BlockData bx = null;
if (ix.startsWith("oraxen:") && Iris.linkOraxen.supported()) {
bx = Iris.linkOraxen.getBlockDataFor(ix.split("\\Q:\\E")[1]);
}
if (bx == null) {
bx = Bukkit.createBlockData(ix);
}
if (bx instanceof Leaves && IrisSettings.get().getGenerator().preventLeafDecay) {
((Leaves) bx).setPersistent(true);
} else if (bx instanceof Leaves) {
((Leaves) bx).setPersistent(false);
}
blockDataCache.put(ix, bx);
return bx;
} catch (Exception e) {
//Iris.reportError(e);
Iris.debug("Failed to load block \"" + ix + "\"");
String block = ix.contains(":") ? ix.split(":")[1].toLowerCase() : ix.toLowerCase();
String state = block.contains("[") ? block.split("\\[")[1].split("\\]")[0] : "";
Map<String, String> stateMap = new HashMap<>();
if (!state.equals("")) {
Arrays.stream(state.split(",")).forEach(s -> {
stateMap.put(s.split("=")[0], s.split("=")[1]);
});
}
block = block.split("\\[")[0];
switch (block) {
case "cauldron" -> block = "water_cauldron"; //Would fail to load if it has a level parameter
case "grass_path" -> block = "dirt_path";
case "concrete" -> block = "white_concrete";
case "wool" -> block = "white_wool";
case "beetroots" -> {
if (stateMap.containsKey("age")) {
String updated = stateMap.get("age");
switch (updated) {
case "7" -> updated = "3";
case "3", "4", "5" -> updated = "2";
case "1", "2" -> updated = "1";
}
stateMap.put("age", updated);
}
}
}
Map<String, String> newStates = new HashMap<>();
for (String key : stateMap.keySet()) { //Iterate through every state and check if its valid
try {
String newState = block + "[" + key + "=" + stateMap.get(key) + "]";
Bukkit.createBlockData(newState);
//If we get to here, the state is okay so we can use it
newStates.put(key, stateMap.get(key));
} catch (IllegalArgumentException ignored) {
}
}
//Combine all the "good" states again
state = newStates.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(Collectors.joining(","));
if (!state.equals("")) state = "[" + state + "]";
String newBlock = block + state;
Iris.debug("Converting " + ix + " to " + newBlock);
try {
BlockData bd = Bukkit.createBlockData(newBlock);
blockDataCache.put(ix, bd);
return bd;
} catch (Throwable e1) {
Iris.reportError(e1);
}
nullBlockDataCache.add(ix);
return null;
}
}
private static BlockData parseBlockData(String ix) {
BlockData bd = parseBlockDataOrNull(ix);
if (bd != null) {
return bd;
}
Iris.warn("Unknown Block Data: " + ix);
return AIR;
}
public static boolean isStorage(BlockData mat) {
Material mm = mat.getMaterial();
Boolean f = storageCache.get(mm);
if (f != null) {
return f;
}
f = mm.equals(B.getMaterial("CHEST"))
|| mm.equals(B.getMaterial("TRAPPED_CHEST"))
|| mm.equals(B.getMaterial("SHULKER_BOX"))
|| mm.equals(B.getMaterial("WHITE_SHULKER_BOX"))
|| mm.equals(B.getMaterial("ORANGE_SHULKER_BOX"))
|| mm.equals(B.getMaterial("MAGENTA_SHULKER_BOX"))
|| mm.equals(B.getMaterial("LIGHT_BLUE_SHULKER_BOX"))
|| mm.equals(B.getMaterial("YELLOW_SHULKER_BOX"))
|| mm.equals(B.getMaterial("LIME_SHULKER_BOX"))
|| mm.equals(B.getMaterial("PINK_SHULKER_BOX"))
|| mm.equals(B.getMaterial("GRAY_SHULKER_BOX"))
|| mm.equals(B.getMaterial("LIGHT_GRAY_SHULKER_BOX"))
|| mm.equals(B.getMaterial("CYAN_SHULKER_BOX"))
|| mm.equals(B.getMaterial("PURPLE_SHULKER_BOX"))
|| mm.equals(B.getMaterial("BLUE_SHULKER_BOX"))
|| mm.equals(B.getMaterial("BROWN_SHULKER_BOX"))
|| mm.equals(B.getMaterial("GREEN_SHULKER_BOX"))
|| mm.equals(B.getMaterial("RED_SHULKER_BOX"))
|| mm.equals(B.getMaterial("BLACK_SHULKER_BOX"))
|| mm.equals(B.getMaterial("BARREL"))
|| mm.equals(B.getMaterial("DISPENSER"))
|| mm.equals(B.getMaterial("DROPPER"))
|| mm.equals(B.getMaterial("HOPPER"))
|| mm.equals(B.getMaterial("FURNACE"))
|| mm.equals(B.getMaterial("BLAST_FURNACE"))
|| mm.equals(B.getMaterial("SMOKER"));
storageCache.put(mm, f);
return f;
}
public static boolean isStorageChest(BlockData mat) {
if (!isStorage(mat)) {
return false;
}
Material mm = mat.getMaterial();
Boolean f = storageChestCache.get(mm);
if (f != null) {
return f;
}
f = mm.equals(B.getMaterial("CHEST"))
|| mm.equals(B.getMaterial("TRAPPED_CHEST"))
|| mm.equals(B.getMaterial("SHULKER_BOX"))
|| mm.equals(B.getMaterial("WHITE_SHULKER_BOX"))
|| mm.equals(B.getMaterial("ORANGE_SHULKER_BOX"))
|| mm.equals(B.getMaterial("MAGENTA_SHULKER_BOX"))
|| mm.equals(B.getMaterial("LIGHT_BLUE_SHULKER_BOX"))
|| mm.equals(B.getMaterial("YELLOW_SHULKER_BOX"))
|| mm.equals(B.getMaterial("LIME_SHULKER_BOX"))
|| mm.equals(B.getMaterial("PINK_SHULKER_BOX"))
|| mm.equals(B.getMaterial("GRAY_SHULKER_BOX"))
|| mm.equals(B.getMaterial("LIGHT_GRAY_SHULKER_BOX"))
|| mm.equals(B.getMaterial("CYAN_SHULKER_BOX"))
|| mm.equals(B.getMaterial("PURPLE_SHULKER_BOX"))
|| mm.equals(B.getMaterial("BLUE_SHULKER_BOX"))
|| mm.equals(B.getMaterial("BROWN_SHULKER_BOX"))
|| mm.equals(B.getMaterial("GREEN_SHULKER_BOX"))
|| mm.equals(B.getMaterial("RED_SHULKER_BOX"))
|| mm.equals(B.getMaterial("BLACK_SHULKER_BOX"))
|| mm.equals(B.getMaterial("BARREL"))
|| mm.equals(B.getMaterial("DISPENSER"))
|| mm.equals(B.getMaterial("DROPPER"))
|| mm.equals(B.getMaterial("HOPPER"));
storageChestCache.put(mm, f);
return f;
}
public static boolean isLit(BlockData mat) {
Material mm = mat.getMaterial();
Boolean f = litCache.get(mm);
if (f != null) {
return f;
}
f = mm.equals(B.getMaterial("GLOWSTONE"))
|| mm.equals(B.getMaterial("END_ROD"))
|| mm.equals(B.getMaterial("SOUL_SAND"))
|| mm.equals(B.getMaterial("TORCH"))
|| mm.equals(Material.REDSTONE_TORCH)
|| mm.equals(B.getMaterial("SOUL_TORCH"))
|| mm.equals(Material.REDSTONE_WALL_TORCH)
|| mm.equals(Material.WALL_TORCH)
|| mm.equals(B.getMaterial("SOUL_WALL_TORCH"))
|| mm.equals(B.getMaterial("LANTERN"))
|| mm.equals(Material.JACK_O_LANTERN)
|| mm.equals(Material.REDSTONE_LAMP)
|| mm.equals(Material.MAGMA_BLOCK)
|| mm.equals(B.getMaterial("SHROOMLIGHT"))
|| mm.equals(B.getMaterial("SEA_LANTERN"))
|| mm.equals(B.getMaterial("SOUL_LANTERN"))
|| mm.equals(Material.FIRE)
|| mm.equals(B.getMaterial("SOUL_FIRE"))
|| mm.equals(B.getMaterial("SEA_PICKLE"))
|| mm.equals(Material.BREWING_STAND)
|| mm.equals(Material.REDSTONE_ORE);
litCache.put(mm, f);
return f;
}
public static boolean isUpdatable(BlockData mat) {
Boolean u = updatableCache.get(mat.getMaterial());
if (u != null) {
return u;
}
u = isLit(mat) || isStorage(mat);
updatableCache.put(mat.getMaterial(), u);
return u;
}
public static boolean isFoliage(Material d) {
return isFoliage(d.createBlockData());
}
public static boolean isFoliage(BlockData d) {
Boolean f = foliageCache.get(d.getMaterial());
if (f != null) {
return f;
}
if (isFluid(d) || isAir(d) || isSolid(d)) {
foliageCache.put(d.getMaterial(), false);
return false;
}
Material mat = d.getMaterial();
f = mat.equals(Material.POPPY)
|| mat.equals(Material.DANDELION)
|| mat.equals(B.getMaterial("CORNFLOWER"))
|| mat.equals(B.getMaterial("SWEET_BERRY_BUSH"))
|| mat.equals(B.getMaterial("CRIMSON_ROOTS"))
|| mat.equals(B.getMaterial("WARPED_ROOTS"))
|| mat.equals(B.getMaterial("NETHER_SPROUTS"))
|| mat.equals(B.getMaterial("ALLIUM"))
|| mat.equals(B.getMaterial("AZURE_BLUET"))
|| mat.equals(B.getMaterial("BLUE_ORCHID"))
|| mat.equals(B.getMaterial("POPPY"))
|| mat.equals(B.getMaterial("DANDELION"))
|| mat.equals(B.getMaterial("OXEYE_DAISY"))
|| mat.equals(B.getMaterial("LILY_OF_THE_VALLEY"))
|| mat.equals(B.getMaterial("WITHER_ROSE"))
|| mat.equals(Material.DARK_OAK_SAPLING)
|| mat.equals(Material.ACACIA_SAPLING)
|| mat.equals(Material.JUNGLE_SAPLING)
|| mat.equals(Material.BIRCH_SAPLING)
|| mat.equals(Material.SPRUCE_SAPLING)
|| mat.equals(Material.OAK_SAPLING)
|| mat.equals(Material.ORANGE_TULIP)
|| mat.equals(Material.PINK_TULIP)
|| mat.equals(Material.RED_TULIP)
|| mat.equals(Material.WHITE_TULIP)
|| mat.equals(Material.FERN)
|| mat.equals(Material.LARGE_FERN)
|| mat.equals(Material.GRASS)
|| mat.equals(Material.TALL_GRASS);
foliageCache.put(d.getMaterial(), f);
return f;
return IntSets.unmodifiable(b);
}
public static boolean canPlaceOnto(Material mat, Material onto) {
String key = mat.name() + "" + onto.name();
if (isFoliage(mat)) {
if (!isFoliagePlantable(onto)) {
return false;
}
}
if (onto.equals(Material.AIR) || onto.equals(B.getMaterial("CAVE_AIR")) || onto.equals(B.getMaterial("VOID_AIR"))) {
if (onto.equals(Material.AIR) ||
onto.equals(B.getMaterial("CAVE_AIR"))
|| onto.equals(B.getMaterial("VOID_AIR"))) {
return false;
}
@@ -447,86 +256,6 @@ public class B {
return true;
}
public static boolean isDecorant(BlockData m) {
Material mm = m.getMaterial();
Boolean f = decorantCache.get(mm);
if (f != null) {
return f;
}
f = mm.equals(Material.GRASS)
|| mm.equals(Material.TALL_GRASS)
|| mm.equals(Material.FERN)
|| mm.equals(Material.LARGE_FERN)
|| mm.equals(B.getMaterial("CORNFLOWER"))
|| mm.equals(Material.SUNFLOWER)
|| mm.equals(Material.CHORUS_FLOWER)
|| mm.equals(Material.POPPY)
|| mm.equals(Material.DANDELION)
|| mm.equals(Material.OXEYE_DAISY)
|| mm.equals(Material.ORANGE_TULIP)
|| mm.equals(Material.PINK_TULIP)
|| mm.equals(Material.RED_TULIP)
|| mm.equals(Material.WHITE_TULIP)
|| mm.equals(Material.LILAC)
|| mm.equals(Material.DEAD_BUSH)
|| mm.equals(B.getMaterial("SWEET_BERRY_BUSH"))
|| mm.equals(Material.ROSE_BUSH)
|| mm.equals(B.getMaterial("WITHER_ROSE"))
|| mm.equals(Material.ALLIUM)
|| mm.equals(Material.BLUE_ORCHID)
|| mm.equals(B.getMaterial("LILY_OF_THE_VALLEY"))
|| mm.equals(B.getMaterial("CRIMSON_FUNGUS"))
|| mm.equals(B.getMaterial("WARPED_FUNGUS"))
|| mm.equals(Material.RED_MUSHROOM)
|| mm.equals(Material.BROWN_MUSHROOM)
|| mm.equals(B.getMaterial("CRIMSON_ROOTS"))
|| mm.equals(B.getMaterial("AZURE_BLUET"))
|| mm.equals(B.getMaterial("WEEPING_VINES"))
|| mm.equals(B.getMaterial("WEEPING_VINES_PLANT"))
|| mm.equals(B.getMaterial("WARPED_ROOTS"))
|| mm.equals(B.getMaterial("NETHER_SPROUTS"))
|| mm.equals(B.getMaterial("TWISTING_VINES"))
|| mm.equals(B.getMaterial("TWISTING_VINES_PLANT"))
|| mm.equals(Material.SUGAR_CANE)
|| mm.equals(Material.WHEAT)
|| mm.equals(Material.POTATOES)
|| mm.equals(Material.CARROTS)
|| mm.equals(Material.BEETROOTS)
|| mm.equals(Material.NETHER_WART)
|| mm.equals(B.getMaterial("SEA_PICKLE"))
|| mm.equals(B.getMaterial("SEAGRASS"))
|| mm.equals(B.getMaterial("ACACIA_BUTTON"))
|| mm.equals(B.getMaterial("BIRCH_BUTTON"))
|| mm.equals(B.getMaterial("CRIMSON_BUTTON"))
|| mm.equals(B.getMaterial("DARK_OAK_BUTTON"))
|| mm.equals(B.getMaterial("JUNGLE_BUTTON"))
|| mm.equals(B.getMaterial("OAK_BUTTON"))
|| mm.equals(B.getMaterial("POLISHED_BLACKSTONE_BUTTON"))
|| mm.equals(B.getMaterial("SPRUCE_BUTTON"))
|| mm.equals(B.getMaterial("STONE_BUTTON"))
|| mm.equals(B.getMaterial("WARPED_BUTTON"))
|| mm.equals(Material.TORCH)
|| mm.equals(B.getMaterial("SOUL_TORCH"));
decorantCache.put(mm, f);
return f;
}
public static KList<BlockData> get(KList<String> find) {
KList<BlockData> b = new KList<>();
for (String i : find) {
BlockData bd = get(i);
if (bd != null) {
b.add(bd);
}
}
return b;
}
public static boolean isFoliagePlantable(BlockData d) {
return d.getMaterial().equals(Material.GRASS_BLOCK)
|| d.getMaterial().equals(Material.ROOTED_DIRT)
@@ -543,6 +272,207 @@ public class B {
|| d.equals(Material.PODZOL);
}
public static boolean isWater(BlockData b) {
return b.getMaterial().equals(Material.WATER);
}
public static BlockData getAir() {
return AIR;
}
public static Material getMaterialOrNull(String bdx) {
try {
return Material.valueOf(bdx.trim().toUpperCase());
} catch (Throwable e) {
Iris.reportError(e);
if(clw.flip())
{
Iris.warn("Unknown Material: " + bdx);
}
return null;
}
}
public static Material getMaterial(String bdx) {
Material m = getMaterialOrNull(bdx);
if(m == null)
{
return AIR_MATERIAL;
}
return m;
}
public static boolean isSolid(BlockData mat) {
return mat.getMaterial().isSolid();
}
public static BlockData getOrNull(String bdxf) {
try {
String bd = bdxf.trim();
BlockData bdx = parseBlockData(bd);
if (bdx == null) {
if(clw.flip())
{
Iris.warn("Unknown Block Data '" + bd + "'");
}
return AIR;
}
return bdx;
} catch (Throwable e) {
Iris.reportError(e);
if(clw.flip())
{
Iris.warn("Unknown Block Data '" + bdxf + "'");
}
}
return null;
}
public static BlockData get(String bdxf) {
BlockData bd = getOrNull(bdxf);
if (bd != null) {
return bd;
}
return AIR;
}
private static BlockData parseBlockData(String ix) {
try {
BlockData bb = blockDataCache.get(ix);
if (bb != null) {
return bb;
}
BlockData bx = null;
if (ix.startsWith("oraxen:") && Iris.linkOraxen.supported()) {
bx = Iris.linkOraxen.getBlockDataFor(ix.split("\\Q:\\E")[1]);
}
if (bx == null) {
bx = Bukkit.createBlockData(ix);
}
if (bx instanceof Leaves && IrisSettings.get().getGenerator().preventLeafDecay) {
((Leaves) bx).setPersistent(true);
} else if (bx instanceof Leaves) {
((Leaves) bx).setPersistent(false);
}
blockDataCache.put(ix, bx);
return bx;
} catch (Throwable e) {
if(clw.flip())
{
Iris.warn("Unknown Block Data: " + ix);
}
String block = ix.contains(":") ? ix.split(":")[1].toLowerCase() : ix.toLowerCase();
String state = block.contains("[") ? block.split("\\Q[\\E")[1].split("\\Q]\\E")[0] : "";
Map<String, String> stateMap = new HashMap<>();
if (!state.equals("")) {
Arrays.stream(state.split(",")).forEach(s -> stateMap.put(s.split("=")[0], s.split("=")[1]));
}
block = block.split("\\Q[\\E")[0];
switch (block) {
case "cauldron" -> block = "water_cauldron";
case "grass_path" -> block = "dirt_path";
case "concrete" -> block = "white_concrete";
case "wool" -> block = "white_wool";
case "beetroots" -> {
if (stateMap.containsKey("age")) {
String updated = stateMap.get("age");
switch (updated) {
case "7" -> updated = "3";
case "3", "4", "5" -> updated = "2";
case "1", "2" -> updated = "1";
}
stateMap.put("age", updated);
}
}
}
Map<String, String> newStates = new HashMap<>();
for (String key : stateMap.keySet()) { //Iterate through every state and check if its valid
try {
String newState = block + "[" + key + "=" + stateMap.get(key) + "]";
Bukkit.createBlockData(newState);
newStates.put(key, stateMap.get(key));
} catch (IllegalArgumentException ignored) {
}
}
//Combine all the "good" states again
state = newStates.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(Collectors.joining(","));
if (!state.equals("")) state = "[" + state + "]";
String newBlock = block + state;
Iris.debug("Converting " + ix + " to " + newBlock);
try {
BlockData bd = Bukkit.createBlockData(newBlock);
blockDataCache.put(ix, bd);
return bd;
} catch (Throwable e1) {
Iris.reportError(e1);
}
return null;
}
}
public static boolean isStorage(BlockData mat) {
return storageCache.contains(mat.getMaterial().ordinal());
}
public static boolean isStorageChest(BlockData mat) {
return storageChestCache.contains(mat.getMaterial().ordinal());
}
public static boolean isLit(BlockData mat) {
return litCache.contains(mat.getMaterial().ordinal());
}
public static boolean isUpdatable(BlockData mat) {
return isLit(mat) || isStorage(mat);
}
public static boolean isFoliage(Material d) {
return foliageCache.contains(d.ordinal());
}
public static boolean isFoliage(BlockData d) {
return isFoliage(d.getMaterial());
}
public static boolean isDecorant(BlockData m) {
return decorantCache.contains(m.getMaterial().ordinal());
}
public static KList<BlockData> get(KList<String> find) {
KList<BlockData> b = new KList<>();
for (String i : find) {
BlockData bd = get(i);
if (bd != null) {
b.add(bd);
}
}
return b;
}
public static boolean isFluid(BlockData d) {
return d.getMaterial().equals(Material.WATER) || d.getMaterial().equals(Material.LAVA);
}

View File

@@ -0,0 +1,123 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2021 Arcane Arts (Volmit Software)
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.volmit.iris.util.hunk.storage;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.function.Consumer4;
import com.volmit.iris.util.function.Consumer4IO;
import com.volmit.iris.util.hunk.Hunk;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings({"DefaultAnnotationParam", "Lombok"})
@Data
@EqualsAndHashCode(callSuper = false)
public class MappedSyncHunk<T> extends StorageHunk<T> implements Hunk<T> {
private final Map<Integer, T> data;
public MappedSyncHunk(int w, int h, int d) {
super(w, h, d);
data = new HashMap<>();
}
public int getEntryCount() {
return data.size();
}
public boolean isMapped() {
return true;
}
public boolean isEmpty() {
synchronized(data)
{
return data.isEmpty();
}
}
@Override
public void setRaw(int x, int y, int z, T t) {
synchronized(data) {
if (t == null) {
data.remove(index(x, y, z));
return;
}
data.put(index(x, y, z), t);
}
}
private Integer index(int x, int y, int z) {
return (z * getWidth() * getHeight()) + (y * getWidth()) + x;
}
@Override
public synchronized Hunk<T> iterateSync(Consumer4<Integer, Integer, Integer, T> c) {
synchronized(data)
{
int idx, z;
for (Map.Entry<Integer, T> g : data.entrySet()) {
idx = g.getKey();
z = idx / (getWidth() * getHeight());
idx -= (z * getWidth() * getHeight());
c.accept(idx % getWidth(), idx / getWidth(), z, g.getValue());
}
return this;
}
}
@Override
public synchronized Hunk<T> iterateSyncIO(Consumer4IO<Integer, Integer, Integer, T> c) throws IOException {
synchronized(data)
{
int idx, z;
for (Map.Entry<Integer, T> g : data.entrySet()) {
idx = g.getKey();
z = idx / (getWidth() * getHeight());
idx -= (z * getWidth() * getHeight());
c.accept(idx % getWidth(), idx / getWidth(), z, g.getValue());
}
return this;
}
}
@Override
public void empty(T b) {
synchronized(data)
{
data.clear();
}
}
@Override
public T getRaw(int x, int y, int z) {
synchronized(data)
{
return data.get(index(x, y, z));
}
}
}

View File

@@ -18,8 +18,13 @@
package com.volmit.iris.util.mantle;
import com.google.common.collect.ImmutableList;
import com.volmit.iris.Iris;
import com.volmit.iris.engine.data.cache.Cache;
import com.volmit.iris.engine.mantle.EngineMantle;
import com.volmit.iris.engine.mantle.MantleWriter;
import com.volmit.iris.engine.object.basic.IrisPosition;
import com.volmit.iris.engine.object.feature.IrisFeaturePositional;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.collection.KSet;
import com.volmit.iris.util.documentation.BlockCoordinates;
@@ -28,19 +33,23 @@ import com.volmit.iris.util.documentation.RegionCoordinates;
import com.volmit.iris.util.format.C;
import com.volmit.iris.util.format.Form;
import com.volmit.iris.util.function.Consumer4;
import com.volmit.iris.util.math.INode;
import com.volmit.iris.util.math.KochanekBartelsInterpolation;
import com.volmit.iris.util.math.M;
import com.volmit.iris.util.math.PathInterpolation;
import com.volmit.iris.util.matter.Matter;
import com.volmit.iris.util.parallel.BurstExecutor;
import com.volmit.iris.util.parallel.HyperLock;
import com.volmit.iris.util.parallel.MultiBurst;
import org.bukkit.util.Vector;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -75,10 +84,17 @@ public class Mantle {
unload = new KSet<>();
loadedRegions = new KMap<>();
lastUse = new KMap<>();
ioBurst = new MultiBurst("Iris Mantle[" + dataFolder.hashCode() + "]", Thread.MIN_PRIORITY, Runtime.getRuntime().availableProcessors() / 2);
ioBurst = MultiBurst.burst;
Iris.debug("Opened The Mantle " + C.DARK_AQUA + dataFolder.getAbsolutePath());
}
/**
* Raise a flag if it is lowered currently, If the flag was raised, execute the runnable
* @param x the chunk x
* @param z the chunk z
* @param flag the flag to raise
* @param r the runnable to fire if the flag is now raised (and was previously lowered)
*/
@ChunkCoordinates
public void raiseFlag(int x, int z, MantleFlag flag, Runnable r) {
if (!hasFlag(x, z, flag)) {
@@ -87,6 +103,27 @@ public class Mantle {
}
}
/**
* Obtain a cached writer which only contains cached chunks.
* This avoids locking on regions when writing to lots of chunks
* @param x the x chunk
* @param z the z chunk
* @param radius the radius chunks
* @return the writer
*/
@ChunkCoordinates
public MantleWriter write(EngineMantle engineMantle, int x, int z, int radius)
{
return new MantleWriter(engineMantle, this, x, z, radius);
}
/**
* Lower a flag if it is raised. If the flag was lowered (meaning it was previously raised), execute the runnable
* @param x the chunk x
* @param z the chunk z
* @param flag the flag to lower
* @param r the runnable that is fired if the flag was raised but is now lowered
*/
@ChunkCoordinates
public void lowerFlag(int x, int z, MantleFlag flag, Runnable r) {
if (hasFlag(x, z, flag)) {
@@ -95,35 +132,69 @@ public class Mantle {
}
}
@ChunkCoordinates
public MantleChunk getChunk(int x, int z)
{
return get(x>>5, z>>5).getOrCreate(x & 31, z & 31);
}
/**
* Flag or unflag a chunk
* @param x the chunk x
* @param z the chunk z
* @param flag the flag
* @param flagged should it be set to flagged or not
*/
@ChunkCoordinates
public void flag(int x, int z, MantleFlag flag, boolean flagged) {
get(x >> 5, z >> 5).getOrCreate(x & 31, z & 31).flag(flag, flagged);
}
/**
* Check very quickly if a tectonic plate exists via cached or the file system
* @param x the x region coordinate
* @param z the z region coordinate
* @return true if it exists
*/
@RegionCoordinates
public boolean hasTectonicPlate(int x, int z)
{
Long k = key(x, z);
return loadedRegions.containsKey(k) || fileForRegion(dataFolder, k).exists();
}
/**
* Iterate data in a chunk
* @param x the chunk x
* @param z the chunk z
* @param type the type of data to iterate
* @param iterator the iterator (x,y,z,data) -> do stuff
* @param <T> the type of data to iterate
*/
@ChunkCoordinates
public <T> void iterateChunk(int x, int z, Class<T> type, Consumer4<Integer, Integer, Integer, T> iterator, MantleFlag... requiredFlags) {
for (MantleFlag i : requiredFlags) {
if (!hasFlag(x, z, i)) {
return;
}
public <T> void iterateChunk(int x, int z, Class<T> type, Consumer4<Integer, Integer, Integer, T> iterator) {
if(!hasTectonicPlate(x >> 5, z >> 5))
{
return;
}
get(x >> 5, z >> 5).getOrCreate(x & 31, z & 31).iterate(type, iterator);
}
@ChunkCoordinates
public <T> void iterateChunk(int x, int z, Class<T> type, Consumer4<Integer, Integer, Integer, T> iterator, BurstExecutor e, MantleFlag... requiredFlags) {
for (MantleFlag i : requiredFlags) {
if (!hasFlag(x, z, i)) {
return;
}
}
get(x >> 5, z >> 5).getOrCreate(x & 31, z & 31).iterate(type, iterator, e);
}
/**
* Does this chunk have a flag on it?
* @param x the x
* @param z the z
* @param flag the flag to test
* @return true if it's flagged
*/
@ChunkCoordinates
public boolean hasFlag(int x, int z, MantleFlag flag) {
if(!hasTectonicPlate(x >> 5, z >> 5))
{
return false;
}
return get(x >> 5, z >> 5).getOrCreate(x & 31, z & 31).isFlagged(flag);
}
@@ -151,11 +222,20 @@ public class Mantle {
return;
}
Matter matter = get((x >> 4) >> 5, (z >> 4) >> 5)
.getOrCreate((x >> 4) & 31, (z >> 4) & 31)
.getOrCreate(y >> 4);
matter.slice(matter.getClass(t))
.set(x & 15, y & 15, z & 15, t);
if(t instanceof IrisFeaturePositional)
{
get((x >> 4) >> 5, (z >> 4) >> 5)
.getOrCreate((x >> 4) & 31, (z >> 4) & 31).addFeature((IrisFeaturePositional) t);
}
else
{
Matter matter = get((x >> 4) >> 5, (z >> 4) >> 5)
.getOrCreate((x >> 4) & 31, (z >> 4) & 31)
.getOrCreate(y >> 4);
matter.slice(matter.getClass(t))
.set(x & 15, y & 15, z & 15, t);
}
}
/**
@@ -180,7 +260,12 @@ public class Mantle {
throw new RuntimeException("The Mantle is closed");
}
if (y < 0) {
if(!hasTectonicPlate((x >> 4) >> 5, (z >> 4) >> 5))
{
return null;
}
if (y < 0 || y >= worldHeight) {
return null;
}
@@ -190,6 +275,10 @@ public class Mantle {
.get(x & 15, y & 15, z & 15);
}
/**
* Is this mantle closed
* @return true if it is
*/
public boolean isClosed()
{
return closed.get();
@@ -228,7 +317,6 @@ public class Mantle {
Iris.reportError(e);
}
ioBurst.shutdownNow();
Iris.debug("The Mantle has Closed " + C.DARK_AQUA + dataFolder.getAbsolutePath());
}
@@ -324,7 +412,7 @@ public class Mantle {
* @return the future of a tectonic plate.
*/
@RegionCoordinates
private CompletableFuture<TectonicPlate> getSafe(int x, int z) {
private Future<TectonicPlate> getSafe(int x, int z) {
Long k = key(x, z);
TectonicPlate p = loadedRegions.get(k);
@@ -367,17 +455,39 @@ public class Mantle {
}));
}
/**
* Get the file for a region
* @param folder the folder
* @param x the x coord
* @param z the z coord
* @return the file
*/
public static File fileForRegion(File folder, int x, int z) {
return fileForRegion(folder, key(x, z));
}
/**
* Get the file for the given region
* @param folder the data folder
* @param key the region key
* @return the file
*/
public static File fileForRegion(File folder, Long key) {
String id = UUID.nameUUIDFromBytes(("TectonicPlate:" + key).getBytes(StandardCharsets.UTF_8)).toString();
File f = new File(folder, id.substring(0, 2) + "/" + id.split("\\Q-\\E")[3] + "/" + id + ".ttp");
f.getParentFile().mkdirs();
if(!f.getParentFile().exists())
{
f.getParentFile().mkdirs();
}
return f;
}
/**
* Get the long value representing a chunk or region coordinate
* @param x the x
* @param z the z
* @return the value
*/
public static Long key(int x, int z) {
return Cache.key(x, z);
}
@@ -385,4 +495,440 @@ public class Mantle {
public void saveAll() {
}
/**
* Set a sphere into the mantle
* @param cx the center x
* @param cy the center y
* @param cz the center z
* @param radius the radius of this sphere
* @param fill should it be filled? or just the outer shell?
* @param data the data to set
* @param <T> the type of data to apply to the mantle
*/
public <T> void setSphere(int cx, int cy, int cz, double radius, boolean fill, T data)
{
setElipsoid(cx, cy, cz, radius, radius, radius, fill, data);
}
/**
* Set an elipsoid into the mantle
* @param cx the center x
* @param cy the center y
* @param cz the center z
* @param rx the x radius
* @param ry the y radius
* @param rz the z radius
* @param fill should it be filled or just the outer shell?
* @param data the data to set
* @param <T> the type of data to apply to the mantle
*/
public <T> void setElipsoid(int cx, int cy, int cz, double rx, double ry, double rz, boolean fill, T data)
{
rx += 0.5;
ry += 0.5;
rz += 0.5;
final double invRadiusX = 1 / rx;
final double invRadiusY = 1 / ry;
final double invRadiusZ = 1 / rz;
final int ceilRadiusX = (int) Math.ceil(rx);
final int ceilRadiusY = (int) Math.ceil(ry);
final int ceilRadiusZ = (int) Math.ceil(rz);
double nextXn = 0;
forX: for (int x = 0; x <= ceilRadiusX; ++x) {
final double xn = nextXn;
nextXn = (x + 1) * invRadiusX;
double nextYn = 0;
forY: for (int y = 0; y <= ceilRadiusY; ++y) {
final double yn = nextYn;
nextYn = (y + 1) * invRadiusY;
double nextZn = 0;
for (int z = 0; z <= ceilRadiusZ; ++z) {
final double zn = nextZn;
nextZn = (z + 1) * invRadiusZ;
double distanceSq = lengthSq(xn, yn, zn);
if (distanceSq > 1) {
if (z == 0) {
if (y == 0) {
break forX;
}
break forY;
}
break;
}
if (!fill) {
if (lengthSq(nextXn, yn, zn) <= 1 && lengthSq(xn, nextYn, zn) <= 1 && lengthSq(xn, yn, nextZn) <= 1) {
continue;
}
}
set(x + cx,y + cy,z + cz, data);
set(-x + cx,y + cy,z + cz, data);
set(x + cx,-y + cy,z + cz, data);
set(x + cx,y + cy,-z + cz, data);
set(-x + cx,y + cy,-z + cz, data);
set(-x + cx,-y + cy,z + cz, data);
set(x + cx,-y + cy,-z + cz, data);
set(-x + cx,y + cy,-z + cz, data);
set(-x + cx,-y + cy,-z + cz, data);
}
}
}
}
/**
* Set a cuboid of data in the mantle
* @param x1 the min x
* @param y1 the min y
* @param z1 the min z
* @param x2 the max x
* @param y2 the max y
* @param z2 the max z
* @param data the data to set
* @param <T> the type of data to apply to the mantle
*/
public <T> void setCuboid(int x1, int y1, int z1, int x2, int y2, int z2, T data)
{
int j,k;
for(int i = x1; i <= x2; i++)
{
for(j = x1; j <= x2; j++)
{
for(k = x1; k <= x2; k++)
{
set(i,j,k,data);
}
}
}
}
/**
* Set a pyramid of data in the mantle
* @param cx the center x
* @param cy the base y
* @param cz the center z
* @param data the data to set
* @param size the size of the pyramid (width of base & height)
* @param filled should it be filled or hollow
* @param <T> the type of data to apply to the mantle
*/
@SuppressWarnings("ConstantConditions")
public <T> void setPyramid(int cx, int cy, int cz, T data, int size, boolean filled) {
int height = size;
for (int y = 0; y <= height; ++y) {
size--;
for (int x = 0; x <= size; ++x) {
for (int z = 0; z <= size; ++z) {
if ((filled && z <= size && x <= size) || z == size || x == size) {
set(x + cx, y + cy, z + cz, data);
set(-x + cx, y + cy, z + cz, data);
set(x + cx, y + cy, -z + cz, data);
set(-x + cx, y + cy, -z + cz, data);
}
}
}
}
}
/**
* Set a 3d tube spline interpolated with Kochanek Bartels
* @param nodevectors the vector points
* @param radius the radius
* @param filled if it should be filled or hollow
* @param data the data to set
*/
public <T> void setSpline(List<Vector> nodevectors, double radius, boolean filled, T data) {
setSpline(nodevectors, 0, 0, 0, 10, radius, filled, data);
}
/**
* Set a 3d tube spline interpolated with Kochanek Bartels
* @param nodevectors the spline points
* @param tension the tension 0
* @param bias the bias 0
* @param continuity the continuity 0
* @param quality the quality 10
* @param radius the radius
* @param filled filled or hollow
* @param data the data to set
* @param <T> the type of data to apply to the mantle
*/
public <T> void setSpline(List<Vector> nodevectors, double tension, double bias, double continuity, double quality, double radius, boolean filled, T data) {
Set<IrisPosition> vset = new KSet<>();
List<INode> nodes = new ArrayList<>(nodevectors.size());
PathInterpolation interpol = new KochanekBartelsInterpolation();
for (Vector nodevector : nodevectors) {
INode n = new INode(nodevector);
n.setTension(tension);
n.setBias(bias);
n.setContinuity(continuity);
nodes.add(n);
}
interpol.setNodes(nodes);
double splinelength = interpol.arcLength(0, 1);
for (double loop = 0; loop <= 1; loop += 1D / splinelength / quality) {
Vector tipv = interpol.getPosition(loop);
vset.add(new IrisPosition(tipv.toBlockVector()));
}
vset = getBallooned(vset, radius);
if (!filled) {
vset = getHollowed(vset);
}
set(vset, data);
}
/**
* Set a 3d line
* @param a the first point
* @param b the second point
* @param radius the radius
* @param filled hollow or filled?
* @param data the data
* @param <T> the type of data to apply to the mantle
*/
public <T> void setLine(IrisPosition a, IrisPosition b, double radius, boolean filled, T data)
{
setLine(ImmutableList.of(a, b), radius, filled, data);
}
/**
* Set lines for points
* @param vectors the points
* @param radius the radius
* @param filled hollow or filled?
* @param data the data to set
* @param <T> the type of data to apply to the mantle
*/
public <T> void setLine(List<IrisPosition> vectors, double radius, boolean filled, T data) {
Set<IrisPosition> vset = new KSet<>();
for (int i = 0; vectors.size() != 0 && i < vectors.size() - 1; i++) {
IrisPosition pos1 = vectors.get(i);
IrisPosition pos2 = vectors.get(i + 1);
int x1 = pos1.getX();
int y1 = pos1.getY();
int z1 = pos1.getZ();
int x2 = pos2.getX();
int y2 = pos2.getY();
int z2 = pos2.getZ();
int tipx = x1;
int tipy = y1;
int tipz = z1;
int dx = Math.abs(x2 - x1);
int dy = Math.abs(y2 - y1);
int dz = Math.abs(z2 - z1);
if (dx + dy + dz == 0) {
vset.add(new IrisPosition(tipx, tipy, tipz));
continue;
}
int dMax = Math.max(Math.max(dx, dy), dz);
if (dMax == dx) {
for (int domstep = 0; domstep <= dx; domstep++) {
tipx = x1 + domstep * (x2 - x1 > 0 ? 1 : -1);
tipy = (int) Math.round(y1 + domstep * ((double) dy) / ((double) dx) * (y2 - y1 > 0 ? 1 : -1));
tipz = (int) Math.round(z1 + domstep * ((double) dz) / ((double) dx) * (z2 - z1 > 0 ? 1 : -1));
vset.add(new IrisPosition(tipx, tipy, tipz));
}
} else if (dMax == dy) {
for (int domstep = 0; domstep <= dy; domstep++) {
tipy = y1 + domstep * (y2 - y1 > 0 ? 1 : -1);
tipx = (int) Math.round(x1 + domstep * ((double) dx) / ((double) dy) * (x2 - x1 > 0 ? 1 : -1));
tipz = (int) Math.round(z1 + domstep * ((double) dz) / ((double) dy) * (z2 - z1 > 0 ? 1 : -1));
vset.add(new IrisPosition(tipx, tipy, tipz));
}
} else /* if (dMax == dz) */ {
for (int domstep = 0; domstep <= dz; domstep++) {
tipz = z1 + domstep * (z2 - z1 > 0 ? 1 : -1);
tipy = (int) Math.round(y1 + domstep * ((double) dy) / ((double) dz) * (y2 - y1 > 0 ? 1 : -1));
tipx = (int) Math.round(x1 + domstep * ((double) dx) / ((double) dz) * (x2 - x1 > 0 ? 1 : -1));
vset.add(new IrisPosition(tipx, tipy, tipz));
}
}
}
vset = getBallooned(vset, radius);
if (!filled) {
vset = getHollowed(vset);
}
set(vset, data);
}
/**
* Set a cylinder in the mantle
* @param cx the center x
* @param cy the base y
* @param cz the center z
* @param data the data to set
* @param radius the radius
* @param height the height of the cyl
* @param filled filled or not
*/
public <T> void setCylinder(int cx, int cy, int cz, T data, double radius, int height, boolean filled){
setCylinder(cx, cy, cz, data, radius, radius, height, filled);
}
/**
* Set a cylinder in the mantle
* @param cx the center x
* @param cy the base y
* @param cz the center z
* @param data the data to set
* @param radiusX the x radius
* @param radiusZ the z radius
* @param height the height of this cyl
* @param filled filled or hollow?
*/
public <T> void setCylinder(int cx, int cy, int cz, T data, double radiusX, double radiusZ, int height, boolean filled) {
int affected = 0;
radiusX += 0.5;
radiusZ += 0.5;
if (height == 0) {
return;
} else if (height < 0) {
height = -height;
cy = cy - height;
}
if (cy < 0) {
cy = 0;
} else if (cy + height - 1 > worldHeight) {
height = worldHeight - cy + 1;
}
final double invRadiusX = 1 / radiusX;
final double invRadiusZ = 1 / radiusZ;
final int ceilRadiusX = (int) Math.ceil(radiusX);
final int ceilRadiusZ = (int) Math.ceil(radiusZ);
double nextXn = 0;
forX: for (int x = 0; x <= ceilRadiusX; ++x) {
final double xn = nextXn;
nextXn = (x + 1) * invRadiusX;
double nextZn = 0;
for (int z = 0; z <= ceilRadiusZ; ++z) {
final double zn = nextZn;
nextZn = (z + 1) * invRadiusZ;
double distanceSq = lengthSq(xn, zn);
if (distanceSq > 1) {
if (z == 0) {
break forX;
}
break;
}
if (!filled) {
if (lengthSq(nextXn, zn) <= 1 && lengthSq(xn, nextZn) <= 1) {
continue;
}
}
for (int y = 0; y < height; ++y) {
set(cx + x, cy + y, cz + z, data);
set(cx + -x, cy + y, cz + z, data);
set(cx + x, cy + y, cz + -z, data);
set(cx + -x, cy + y, cz + -z, data);
}
}
}
}
public <T> void set(IrisPosition pos, T data)
{
set(pos.getX(), pos.getY(), pos.getZ(), data);
}
public <T> void set(List<IrisPosition> positions, T data)
{
for(IrisPosition i : positions)
{
set(i, data);
}
}
public <T> void set(Set<IrisPosition> positions, T data)
{
for(IrisPosition i : positions)
{
set(i, data);
}
}
private static Set<IrisPosition> getBallooned(Set<IrisPosition> vset, double radius) {
Set<IrisPosition> returnset = new HashSet<>();
int ceilrad = (int) Math.ceil(radius);
for (IrisPosition v : vset) {
int tipx = v.getX();
int tipy = v.getY();
int tipz = v.getZ();
for (int loopx = tipx - ceilrad; loopx <= tipx + ceilrad; loopx++) {
for (int loopy = tipy - ceilrad; loopy <= tipy + ceilrad; loopy++) {
for (int loopz = tipz - ceilrad; loopz <= tipz + ceilrad; loopz++) {
if (hypot(loopx - tipx, loopy - tipy, loopz - tipz) <= radius) {
returnset.add(new IrisPosition(loopx, loopy, loopz));
}
}
}
}
}
return returnset;
}
private static Set<IrisPosition> getHollowed(Set<IrisPosition> vset) {
Set<IrisPosition> returnset = new KSet<>();
for (IrisPosition v : vset) {
double x = v.getX();
double y = v.getY();
double z = v.getZ();
if (!(vset.contains(new IrisPosition(x + 1, y, z))
&& vset.contains(new IrisPosition(x - 1, y, z))
&& vset.contains(new IrisPosition(x, y + 1, z))
&& vset.contains(new IrisPosition(x, y - 1, z))
&& vset.contains(new IrisPosition(x, y, z + 1))
&& vset.contains(new IrisPosition(x, y, z - 1)))) {
returnset.add(v);
}
}
return returnset;
}
private static double hypot(double... pars) {
double sum = 0;
for (double d : pars) {
sum += Math.pow(d, 2);
}
return Math.sqrt(sum);
}
private static double lengthSq(double x, double y, double z) {
return (x * x) + (y * y) + (z * z);
}
private static double lengthSq(double x, double z) {
return (x * x) + (z * z);
}
public int getWorldHeight() {
return worldHeight;
}
}

View File

@@ -18,17 +18,22 @@
package com.volmit.iris.util.mantle;
import com.volmit.iris.engine.object.feature.IrisFeaturePositional;
import com.volmit.iris.util.data.Varint;
import com.volmit.iris.util.documentation.ChunkCoordinates;
import com.volmit.iris.util.function.Consumer4;
import com.volmit.iris.util.matter.IrisMatter;
import com.volmit.iris.util.matter.Matter;
import com.volmit.iris.util.matter.MatterSlice;
import com.volmit.iris.util.matter.slices.ZoneMatter;
import com.volmit.iris.util.parallel.BurstExecutor;
import com.volmit.iris.util.parallel.MultiBurst;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.concurrent.atomic.AtomicReferenceArray;
@@ -37,8 +42,10 @@ import java.util.concurrent.atomic.AtomicReferenceArray;
* Mantle Chunks are fully atomic & thread safe
*/
public class MantleChunk {
private static final ZoneMatter zm = new ZoneMatter();
private final AtomicIntegerArray flags;
private final AtomicReferenceArray<Matter> sections;
private final CopyOnWriteArrayList<IrisFeaturePositional> features;
/**
* Create a mantle chunk
@@ -49,6 +56,7 @@ public class MantleChunk {
public MantleChunk(int sectionHeight) {
sections = new AtomicReferenceArray<>(sectionHeight);
flags = new AtomicIntegerArray(MantleFlag.values().length);
features = new CopyOnWriteArrayList<>();
for (int i = 0; i < flags.length(); i++) {
flags.set(i, 0);
@@ -76,6 +84,13 @@ public class MantleChunk {
sections.set(i, Matter.read(din));
}
}
short v = din.readShort();
for(int i = 0; i < v; i++)
{
features.add(zm.readNode(din));
}
}
public void flag(MantleFlag flag, boolean f) {
@@ -169,6 +184,13 @@ public class MantleChunk {
dos.writeBoolean(false);
}
}
dos.writeShort(features.size());
for(IrisFeaturePositional i : features)
{
zm.writeNode(i, dos);
}
}
private void trimSlice(int i) {
@@ -186,26 +208,6 @@ public class MantleChunk {
}
}
public <T> void iterate(Class<T> type, Consumer4<Integer, Integer, Integer, T> iterator, BurstExecutor burst) {
for (int i = 0; i < sections.length(); i++) {
int finalI = i;
burst.queue(() -> {
int bs = (finalI << 4);
Matter matter = get(finalI);
if (matter != null) {
MatterSlice<T> t = matter.getSlice(type);
if (t != null) {
t.iterateSync((a, b, c, f) -> iterator.accept(a, b + bs, c, f));
}
}
});
}
burst.complete();
}
public <T> void iterate(Class<T> type, Consumer4<Integer, Integer, Integer, T> iterator) {
for (int i = 0; i < sections.length(); i++) {
int bs = (i << 4);
@@ -220,4 +222,12 @@ public class MantleChunk {
}
}
}
public void addFeature(IrisFeaturePositional t) {
features.add(t);
}
public List<IrisFeaturePositional> getFeatures() {
return features;
}
}

View File

@@ -19,9 +19,11 @@
package com.volmit.iris.util.mantle;
import com.volmit.iris.Iris;
import com.volmit.iris.engine.data.cache.Cache;
import com.volmit.iris.util.documentation.ChunkCoordinates;
import com.volmit.iris.util.format.C;
import com.volmit.iris.util.format.Form;
import com.volmit.iris.util.hunk.storage.ArrayHunk;
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
import java.io.*;
@@ -140,7 +142,7 @@ public class TectonicPlate {
@ChunkCoordinates
private int index(int x, int z) {
return (x & 0x1F) + (z & 0x1F) * 32;
return Cache.to1D(x, z, 0, 32, 32);
}
/**

View File

@@ -0,0 +1,47 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2021 Arcane Arts (Volmit Software)
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.volmit.iris.util.math;
import lombok.Data;
import org.bukkit.util.Vector;
@Data
public class INode {
private Vector position;
private double tension;
private double bias;
private double continuity;
public INode() {
this(new Vector(0,0,0));
}
public INode(INode other) {
this.position = other.position;
this.tension = other.tension;
this.bias = other.bias;
this.continuity = other.continuity;
}
public INode(Vector position) {
this.position = position;
}
}

View File

@@ -0,0 +1,248 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2021 Arcane Arts (Volmit Software)
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.volmit.iris.util.math;
import org.bukkit.util.Vector;
import java.util.Collections;
import java.util.List;
public class KochanekBartelsInterpolation implements PathInterpolation {
private List<INode> nodes;
private Vector[] coeffA;
private Vector[] coeffB;
private Vector[] coeffC;
private Vector[] coeffD;
private double scaling;
public KochanekBartelsInterpolation() {
setNodes(Collections.emptyList());
}
@Override
public void setNodes(List<INode> nodes) {
this.nodes = nodes;
recalc();
}
private void recalc() {
final int nNodes = nodes.size();
coeffA = new Vector[nNodes];
coeffB = new Vector[nNodes];
coeffC = new Vector[nNodes];
coeffD = new Vector[nNodes];
if (nNodes == 0) {
return;
}
INode nodeB = nodes.get(0);
double tensionB = nodeB.getTension();
double biasB = nodeB.getBias();
double continuityB = nodeB.getContinuity();
for (int i = 0; i < nNodes; ++i) {
final double tensionA = tensionB;
final double biasA = biasB;
final double continuityA = continuityB;
if (i + 1 < nNodes) {
nodeB = nodes.get(i + 1);
tensionB = nodeB.getTension();
biasB = nodeB.getBias();
continuityB = nodeB.getContinuity();
}
// Kochanek-Bartels tangent coefficients
final double ta = (1 - tensionA) * (1 + biasA) * (1 + continuityA) / 2; // Factor for lhs of d[i]
final double tb = (1 - tensionA) * (1 - biasA) * (1 - continuityA) / 2; // Factor for rhs of d[i]
final double tc = (1 - tensionB) * (1 + biasB) * (1 - continuityB) / 2; // Factor for lhs of d[i+1]
final double td = (1 - tensionB) * (1 - biasB) * (1 + continuityB) / 2; // Factor for rhs of d[i+1]
coeffA[i] = linearCombination(i, -ta, ta - tb - tc + 2, tb + tc - td - 2, td);
coeffB[i] = linearCombination(i, 2 * ta, -2 * ta + 2 * tb + tc - 3, -2 * tb - tc + td + 3, -td);
coeffC[i] = linearCombination(i, -ta, ta - tb, tb, 0);
//coeffD[i] = linearCombination(i, 0, 1, 0, 0);
coeffD[i] = retrieve(i); // this is an optimization
}
scaling = nodes.size() - 1;
}
/**
* Returns the linear combination of the given coefficients with the nodes adjacent to baseIndex.
*
* @param baseIndex node index
* @param f1 coefficient for baseIndex-1
* @param f2 coefficient for baseIndex
* @param f3 coefficient for baseIndex+1
* @param f4 coefficient for baseIndex+2
* @return linear combination of nodes[n-1..n+2] with f1..4
*/
private Vector linearCombination(int baseIndex, double f1, double f2, double f3, double f4) {
final Vector r1 = retrieve(baseIndex - 1).multiply(f1);
final Vector r2 = retrieve(baseIndex ).multiply(f2);
final Vector r3 = retrieve(baseIndex + 1).multiply(f3);
final Vector r4 = retrieve(baseIndex + 2).multiply(f4);
return r1.add(r2).add(r3).add(r4);
}
/**
* Retrieves a node. Indexes are clamped to the valid range.
*
* @param index node index to retrieve
* @return nodes[clamp(0, nodes.length-1)]
*/
private Vector retrieve(int index) {
if (index < 0) {
return fastRetrieve(0);
}
if (index >= nodes.size()) {
return fastRetrieve(nodes.size() - 1);
}
return fastRetrieve(index);
}
private Vector fastRetrieve(int index) {
return nodes.get(index).getPosition();
}
@Override
public Vector getPosition(double position) {
if (coeffA == null) {
throw new IllegalStateException("Must call setNodes first.");
}
if (position > 1) {
return null;
}
position *= scaling;
final int index = (int) Math.floor(position);
final double remainder = position - index;
final Vector a = coeffA[index];
final Vector b = coeffB[index];
final Vector c = coeffC[index];
final Vector d = coeffD[index];
return a.multiply(remainder).add(b).multiply(remainder).add(c).multiply(remainder).add(d);
}
@Override
public Vector get1stDerivative(double position) {
if (coeffA == null) {
throw new IllegalStateException("Must call setNodes first.");
}
if (position > 1) {
return null;
}
position *= scaling;
final int index = (int) Math.floor(position);
//final double remainder = position - index;
final Vector a = coeffA[index];
final Vector b = coeffB[index];
final Vector c = coeffC[index];
return a.multiply(1.5 * position - 3.0 * index).add(b).multiply(2.0 * position).add(a.multiply(1.5 * index).subtract(b).multiply(2.0 * index)).add(c).multiply(scaling);
}
@Override
public double arcLength(double positionA, double positionB) {
if (coeffA == null) {
throw new IllegalStateException("Must call setNodes first.");
}
if (positionA > positionB) {
return arcLength(positionB, positionA);
}
positionA *= scaling;
positionB *= scaling;
final int indexA = (int) Math.floor(positionA);
final double remainderA = positionA - indexA;
final int indexB = (int) Math.floor(positionB);
final double remainderB = positionB - indexB;
return arcLengthRecursive(indexA, remainderA, indexB, remainderB);
}
/**
* Assumes a < b.
*/
private double arcLengthRecursive(int indexLeft, double remainderLeft, int indexRight, double remainderRight) {
switch (indexRight - indexLeft) {
case 0:
return arcLengthRecursive(indexLeft, remainderLeft, remainderRight);
case 1:
// This case is merely a speed-up for a very common case
return arcLengthRecursive(indexLeft, remainderLeft, 1.0)
+ arcLengthRecursive(indexRight, 0.0, remainderRight);
default:
return arcLengthRecursive(indexLeft, remainderLeft, indexRight - 1, 1.0)
+ arcLengthRecursive(indexRight, 0.0, remainderRight);
}
}
private double arcLengthRecursive(int index, double remainderLeft, double remainderRight) {
final Vector a = coeffA[index].multiply(3.0);
final Vector b = coeffB[index].multiply(2.0);
final Vector c = coeffC[index];
final int nPoints = 8;
double accum = a.multiply(remainderLeft).add(b).multiply(remainderLeft).add(c).length() / 2.0;
for (int i = 1; i < nPoints - 1; ++i) {
double t = ((double) i) / nPoints;
t = (remainderRight - remainderLeft) * t + remainderLeft;
accum += a.multiply(t).add(b).multiply(t).add(c).length();
}
accum += a.multiply(remainderRight).add(b).multiply(remainderRight).add(c).length() / 2.0;
return accum * (remainderRight - remainderLeft) / nPoints;
}
@Override
public int getSegment(double position) {
if (coeffA == null) {
throw new IllegalStateException("Must call setNodes first.");
}
if (position > 1) {
return Integer.MAX_VALUE;
}
position *= scaling;
return (int) Math.floor(position);
}
}

View File

@@ -0,0 +1,70 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2021 Arcane Arts (Volmit Software)
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.volmit.iris.util.math;
import org.bukkit.util.Vector;
import java.util.List;
public interface PathInterpolation {
/**
* Sets nodes to be used by subsequent calls to
* {@link #getPosition(double)} and the other methods.
*
* @param nodes the nodes
*/
void setNodes(List<INode> nodes);
/**
* Gets the result of f(position).
*
* @param position the position to interpolate
* @return the result
*/
Vector getPosition(double position);
/**
* Gets the result of f'(position).
*
* @param position the position to interpolate
* @return the result
*/
Vector get1stDerivative(double position);
/**
* Gets the result of &int;<sub>a</sub><sup style="position: relative; left: -1ex">b</sup>|f'(t)| dt.<br />
* That means it calculates the arc length (in meters) between positionA
* and positionB.
*
* @param positionA lower limit
* @param positionB upper limit
* @return the arc length
*/
double arcLength(double positionA, double positionB);
/**
* Get the segment position.
*
* @param position the position
* @return the segment position
*/
int getSegment(double position);
}

View File

@@ -27,6 +27,7 @@ import com.volmit.iris.util.hunk.Hunk;
import com.volmit.iris.util.math.BlockPosition;
import org.bukkit.World;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_17_R1.block.data.type.CraftLeaves;
import org.bukkit.entity.Entity;
import java.io.*;
@@ -189,7 +190,16 @@ public interface Matter {
slice = (MatterSlice<T>) createSlice(c, this);
if (slice == null) {
Iris.error("Unable to find a slice for class " + C.DARK_RED + c.getCanonicalName());
try
{
throw new RuntimeException("Bad slice " + c.getCanonicalName());
}
catch(Throwable e)
{
e.printStackTrace();
}
return null;
}

View File

@@ -18,6 +18,7 @@
package com.volmit.iris.util.matter.slices;
import com.volmit.iris.Iris;
import com.volmit.iris.engine.object.feature.IrisFeaturePositional;
import com.volmit.iris.util.matter.Sliced;

View File

@@ -225,7 +225,7 @@ public class Chunk {
* @param blockZ The z-coordinate of the block.
* @return The biome id or -1 if the biomes are not correctly initialized.
*/
public int getBiomeAt(int blockX, int blockY, int blockZ) {
public synchronized int getBiomeAt(int blockX, int blockY, int blockZ) {
if (dataVersion < 2202) {
if (biomes == null || biomes.length != 256) {
return -1;
@@ -244,7 +244,7 @@ public class Chunk {
}
@Deprecated
public void setBiomeAt(int blockX, int blockZ, int biomeID) {
public synchronized void setBiomeAt(int blockX, int blockZ, int biomeID) {
if (dataVersion < 2202) {
if (biomes == null || biomes.length != 256) {
biomes = new int[256];
@@ -275,7 +275,7 @@ public class Chunk {
* @param biomeID The biome id to be set.
* When set to a negative number, Minecraft will replace it with the block column's default biome.
*/
public void setBiomeAt(int blockX, int blockY, int blockZ, int biomeID) {
public synchronized void setBiomeAt(int blockX, int blockY, int blockZ, int biomeID) {
if (dataVersion < 2202) {
if (biomes == null || biomes.length != 256) {
biomes = new int[256];

View File

@@ -27,6 +27,7 @@ import com.volmit.iris.util.format.C;
import com.volmit.iris.util.math.M;
import com.volmit.iris.util.nbt.tag.CompoundTag;
import com.volmit.iris.util.nbt.tag.StringTag;
import com.volmit.iris.util.parallel.HyperLock;
import com.volmit.iris.util.scheduling.IrisLock;
import org.bukkit.NamespacedKey;
import org.bukkit.block.Biome;
@@ -43,8 +44,8 @@ public class NBTWorld {
private static final BlockData AIR = B.get("AIR");
private static final Map<String, CompoundTag> blockDataCache = new KMap<>();
private static final Map<Biome, Integer> biomeIds = computeBiomeIDs();
private final IrisLock regionLock = new IrisLock("Region");
private final KMap<Long, MCAFile> loadedRegions;
private final HyperLock hyperLock = new HyperLock();
private final KMap<Long, Long> lastUse;
private final File worldFolder;
private final ExecutorService saveQueue;
@@ -62,13 +63,11 @@ public class NBTWorld {
}
public void close() {
regionLock.lock();
for (Long i : loadedRegions.k()) {
queueSaveUnload(Cache.keyX(i), Cache.keyZ(i));
}
regionLock.unlock();
saveQueue.shutdown();
try {
while (!saveQueue.awaitTermination(3, TimeUnit.SECONDS)) {
@@ -80,13 +79,9 @@ public class NBTWorld {
}
public void flushNow() {
regionLock.lock();
for (Long i : loadedRegions.k()) {
doSaveUnload(Cache.keyX(i), Cache.keyZ(i));
}
regionLock.unlock();
}
public void queueSaveUnload(int x, int z) {
@@ -103,8 +98,6 @@ public class NBTWorld {
}
public void save() {
regionLock.lock();
boolean saving = true;
for (Long i : loadedRegions.k()) {
@@ -121,8 +114,6 @@ public class NBTWorld {
}
Iris.debug("Regions: " + C.GOLD + loadedRegions.size() + C.LIGHT_PURPLE);
regionLock.unlock();
}
public void queueSave() {
@@ -131,10 +122,8 @@ public class NBTWorld {
public synchronized void unloadRegion(int x, int z) {
long key = Cache.key(x, z);
regionLock.lock();
loadedRegions.remove(key);
lastUse.remove(key);
regionLock.unlock();
Iris.debug("Unloaded Region " + C.GOLD + x + " " + z);
}
@@ -249,6 +238,11 @@ public class NBTWorld {
getChunkSection(x >> 4, y >> 4, z >> 4).setBlockStateAt(x & 15, y & 15, z & 15, getCompound(data), false);
}
public int getBiomeId(Biome b)
{
return biomeIds.get(b);
}
public void setBiome(int x, int y, int z, Biome biome) {
getChunk(x >> 4, z >> 4).setBiomeAt(x & 15, y, z & 15, biomeIds.get(biome));
}
@@ -265,8 +259,12 @@ public class NBTWorld {
return s;
}
public synchronized Chunk getChunk(int x, int z) {
MCAFile mca = getMCA(x >> 5, z >> 5);
public Chunk getChunk(int x, int z)
{
return getChunk(getMCA(x >> 5, z >> 5), x, z);
}
public Chunk getChunk(MCAFile mca, int x, int z) {
Chunk c = mca.getChunk(x & 31, z & 31);
if (c == null) {
@@ -278,41 +276,40 @@ public class NBTWorld {
}
public long getIdleDuration(int x, int z) {
Long l = lastUse.get(Cache.key(x, z));
return l == null ? 0 : (M.ms() - l);
return hyperLock.withResult(x, z, () -> {
Long l = lastUse.get(Cache.key(x, z));
return l == null ? 0 : (M.ms() - l);
});
}
public MCAFile getMCA(int x, int z) {
long key = Cache.key(x, z);
regionLock.lock();
lastUse.put(key, M.ms());
MCAFile mcaf = loadedRegions.get(key);
regionLock.unlock();
return hyperLock.withResult(x, z, () -> {
lastUse.put(key, M.ms());
if (mcaf == null) {
mcaf = new MCAFile(x, z);
regionLock.lock();
loadedRegions.put(key, mcaf);
regionLock.unlock();
}
MCAFile mcaf = loadedRegions.get(key);
return mcaf;
if (mcaf == null) {
mcaf = new MCAFile(x, z);
loadedRegions.put(key, mcaf);
}
return mcaf;
});
}
public MCAFile getMCAOrNull(int x, int z) {
long key = Cache.key(x, z);
MCAFile ff = null;
regionLock.lock();
if (loadedRegions.containsKey(key)) {
lastUse.put(key, M.ms());
ff = loadedRegions.get(key);
}
return hyperLock.withResult(x, z, () -> {
if (loadedRegions.containsKey(key)) {
lastUse.put(key, M.ms());
return loadedRegions.get(key);
}
regionLock.unlock();
return ff;
return null;
});
}
public int size() {

View File

@@ -24,6 +24,7 @@ import com.volmit.iris.util.nbt.tag.ByteArrayTag;
import com.volmit.iris.util.nbt.tag.CompoundTag;
import com.volmit.iris.util.nbt.tag.ListTag;
import com.volmit.iris.util.nbt.tag.LongArrayTag;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import java.util.ArrayList;
import java.util.HashMap;
@@ -170,7 +171,7 @@ public class Section {
* This option should only be used moderately to avoid unnecessary recalculation of the palette indices.
* Recalculating the Palette should only be executed once right before saving the Section to file.
*/
public void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag state, boolean cleanup) {
public synchronized void setBlockStateAt(int blockX, int blockY, int blockZ, CompoundTag state, boolean cleanup) {
int paletteSizeBefore = palette.size();
int paletteIndex = addToPalette(state);
//power of 2 --> bits must increase, but only if the palette size changed
@@ -223,7 +224,7 @@ public class Section {
* @param paletteIndex The block state to be set (index of block data in the palette).
* @param blockStates The block states to be updated.
*/
public void setPaletteIndex(int blockIndex, int paletteIndex, AtomicLongArray blockStates) {
public synchronized void setPaletteIndex(int blockIndex, int paletteIndex, AtomicLongArray blockStates) {
int bits = blockStates.length() >> 6;
if (dataVersion < 2527) {
@@ -253,7 +254,7 @@ public class Section {
return palette;
}
int addToPalette(CompoundTag data) {
synchronized int addToPalette(CompoundTag data) {
PaletteIndex index;
if ((index = getValueIndexedPalette(data)) != null) {
return index.index;
@@ -283,14 +284,14 @@ public class Section {
* This should only be used moderately to avoid unnecessary recalculation of the palette indices.
* Recalculating the Palette should only be executed once right before saving the Section to file.
*/
public void cleanupPaletteAndBlockStates() {
public synchronized void cleanupPaletteAndBlockStates() {
Map<Integer, Integer> oldToNewMapping = cleanupPalette();
adjustBlockStateBits(oldToNewMapping, blockStates);
}
private Map<Integer, Integer> cleanupPalette() {
private synchronized Map<Integer, Integer> cleanupPalette() {
//create index - palette mapping
Map<Integer, Integer> allIndices = new HashMap<>();
Map<Integer, Integer> allIndices = new Int2IntOpenHashMap();
for (int i = 0; i < 4096; i++) {
int paletteIndex = getPaletteIndex(i);
allIndices.put(paletteIndex, paletteIndex);
@@ -314,7 +315,7 @@ public class Section {
return allIndices;
}
void adjustBlockStateBits(Map<Integer, Integer> oldToNewMapping, AtomicLongArray blockStates) {
synchronized void adjustBlockStateBits(Map<Integer, Integer> oldToNewMapping, AtomicLongArray blockStates) {
//increases or decreases the amount of bits used per BlockState
//based on the size of the palette. oldToNewMapping can be used to update indices
//if the palette had been cleaned up before using MCAFile#cleanupPalette().
@@ -376,7 +377,7 @@ public class Section {
* @throws NullPointerException If <code>blockStates</code> is <code>null</code>
* @throws IllegalArgumentException When <code>blockStates</code>' length is &lt; 256 or &gt; 4096 and is not a multiple of 64
*/
public void setBlockStates(AtomicLongArray blockStates) {
public synchronized void setBlockStates(AtomicLongArray blockStates) {
if (blockStates == null) {
throw new NullPointerException("BlockStates cannot be null");
} else if (blockStates.length() % 64 != 0 || blockStates.length() < 256 || blockStates.length() > 4096) {

View File

@@ -26,6 +26,7 @@ import lombok.Data;
@Data
public class WormIterator2 {
private transient Worm2 worm;
private transient NoiseProvider noise;
private int x;
private int z;
private int maxDistance;
@@ -39,13 +40,18 @@ public class WormIterator2 {
+ ((z * z) - (worm.getZ().getPosition() * worm.getZ().getPosition())) < dist * dist;
}
public Worm2 next(NoiseProvider p)
public Worm2 next()
{
if(worm == null)
{
worm = new Worm2(x, z, 0, 0);
return worm;
}
worm.getX().setVelocity(noise.noise(worm.getX().getPosition(), 0));
worm.getZ().setVelocity(noise.noise(worm.getZ().getPosition(), 0));
worm.step();
return worm;
}
}

View File

@@ -0,0 +1,70 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2021 Arcane Arts (Volmit Software)
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.volmit.iris.util.noise;
import com.volmit.iris.Iris;
import com.volmit.iris.util.function.NoiseProvider;
import lombok.Builder;
import lombok.Data;
@Builder
@Data
public class WormIterator3 {
private transient Worm3 worm;
private int x;
private int y;
private int z;
private transient NoiseProvider noise;
private int maxDistance;
private int maxIterations;
public boolean hasNext()
{
if(worm == null)
{
return true;
}
double dist = maxDistance - (Math.max(Math.max(Math.abs(worm.getX().getVelocity()),
Math.abs(worm.getZ().getVelocity())),
Math.abs(worm.getY().getVelocity())) + 1);
return maxIterations > 0 &&
((x * x) - (worm.getX().getPosition() * worm.getX().getPosition()))
+ ((y * y) - (worm.getY().getPosition() * worm.getY().getPosition()))
+ ((z * z) - (worm.getZ().getPosition() * worm.getZ().getPosition())) < dist * dist;
}
public Worm3 next()
{
maxIterations--;
if(worm == null)
{
worm = new Worm3(x, y, z, 0, 0, 0);
return worm;
}
worm.getX().setVelocity(worm.getX().getVelocity() + noise.noise(worm.getX().getPosition() + 10000, 0));
worm.getY().setVelocity(worm.getY().getVelocity() + noise.noise(worm.getY().getPosition() + 1000, 0));
worm.getZ().setVelocity(worm.getZ().getVelocity() + noise.noise(worm.getZ().getPosition() - 10000, 0));
worm.step();
return worm;
}
}

View File

@@ -28,25 +28,20 @@ import java.util.concurrent.*;
@SuppressWarnings("ALL")
public class BurstExecutor {
private final ExecutorService executor;
private final KList<CompletableFuture<Void>> futures;
private final KList<Future<?>> futures;
@Setter
private boolean multicore = true;
public BurstExecutor(ExecutorService executor, int burstSizeEstimate) {
this.executor = executor;
futures = new KList<CompletableFuture<Void>>(burstSizeEstimate);
futures = new KList<Future<?>>(burstSizeEstimate);
}
@SuppressWarnings("UnusedReturnValue")
public CompletableFuture<Void> queue(Runnable r) {
if(!multicore)
{
r.run();
return null;
}
public Future<?> queue(Runnable r) {
synchronized (futures) {
CompletableFuture<Void> c = CompletableFuture.runAsync(r, executor);
Future<?> c = executor.submit(r);
futures.add(c);
return c;
}
@@ -55,7 +50,7 @@ public class BurstExecutor {
public BurstExecutor queue(List<Runnable> r) {
if(!multicore)
{
for(Runnable i : r)
for(Runnable i : new KList<>(r))
{
i.run();
}
@@ -65,8 +60,7 @@ public class BurstExecutor {
synchronized (futures) {
for (Runnable i : new KList<>(r)) {
CompletableFuture<Void> c = CompletableFuture.runAsync(i, executor);
futures.add(c);
queue(i);
}
}
@@ -76,7 +70,7 @@ public class BurstExecutor {
public BurstExecutor queue(Runnable[] r) {
if(!multicore)
{
for(Runnable i : r)
for(Runnable i : new KList<>(r))
{
i.run();
}
@@ -86,8 +80,7 @@ public class BurstExecutor {
synchronized (futures) {
for (Runnable i : r) {
CompletableFuture<Void> c = CompletableFuture.runAsync(i, executor);
futures.add(c);
queue(i);
}
}
@@ -106,38 +99,15 @@ public class BurstExecutor {
}
try {
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get();
futures.clear();
} catch (InterruptedException | ExecutionException e) {
Iris.reportError(e);
}
}
}
public boolean complete(long maxDur) {
if(!multicore)
{
return true;
}
synchronized (futures) {
if (futures.isEmpty()) {
return true;
}
try {
try {
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(maxDur, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
return false;
for(Future<?> i : futures)
{
i.get();
}
futures.clear();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
Iris.reportError(e);
}
}
return false;
}
}

View File

@@ -22,10 +22,8 @@ import com.volmit.iris.Iris;
import com.volmit.iris.core.IrisSettings;
import com.volmit.iris.core.service.PreservationSVC;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.io.InstanceState;
import com.volmit.iris.util.math.M;
import com.volmit.iris.util.scheduling.J;
import com.volmit.iris.util.scheduling.Looper;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.concurrent.*;
@@ -33,66 +31,39 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
public class MultiBurst {
public static final MultiBurst burst = new MultiBurst("Iris", IrisSettings.get().getConcurrency().getMiscThreadPriority(), IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getMiscThreadCount()));
public static final MultiBurst burst = new MultiBurst();
private ExecutorService service;
private final Looper heartbeat;
private final AtomicLong last;
private int tid;
private final String name;
private final int tc;
private final int priority;
private final int instance;
public MultiBurst(int tc) {
this("Iris", 6, tc);
public MultiBurst() {
this("Iris", Thread.MIN_PRIORITY);
}
public MultiBurst(String name, int priority, int tc) {
public MultiBurst(String name, int priority) {
this.name = name;
this.priority = priority;
this.tc = tc;
instance = InstanceState.getInstanceId();
last = new AtomicLong(M.ms());
heartbeat = new Looper() {
@Override
protected long loop() {
if (instance != InstanceState.getInstanceId()) {
shutdownNow();
return -1;
}
if (M.ms() - last.get() > TimeUnit.MINUTES.toMillis(1) && service != null) {
service.shutdown();
service = null;
Iris.debug("Shutting down MultiBurst Pool " + getName() + " to conserve resources.");
}
return 30000;
}
};
heartbeat.setName(name + " Monitor");
heartbeat.start();
Iris.service(PreservationSVC.class).register(this);
}
private synchronized ExecutorService getService() {
last.set(M.ms());
if (service == null || service.isShutdown()) {
service = Executors.newFixedThreadPool(Math.max(tc, 1), r -> {
tid++;
Thread t = new Thread(r);
t.setName(name + " " + tid);
t.setPriority(priority);
t.setUncaughtExceptionHandler((et, e) ->
{
Iris.info("Exception encountered in " + et.getName());
e.printStackTrace();
});
service = new ForkJoinPool(IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism()),
new ForkJoinPool.ForkJoinWorkerThreadFactory() {
int m = 0;
return t;
});
Iris.service(PreservationSVC.class).register(service);
Iris.debug("Started MultiBurst Pool " + name + " with " + tc + " threads at " + priority + " priority.");
@Override
public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
worker.setPriority(priority);
worker.setName(name + " " + ++m);
return worker;
}
},
(t, e) -> e.printStackTrace(), true);
}
return service;
@@ -102,10 +73,40 @@ public class MultiBurst {
burst(r.length).queue(r).complete();
}
public void burst(boolean multicore, Runnable... r) {
if(multicore)
{
burst(r);
}
else
{
sync(r);
}
}
public void burst(List<Runnable> r) {
burst(r.size()).queue(r).complete();
}
public void burst(boolean multicore, List<Runnable> r) {
if(multicore)
{
burst(r);
}
else {
sync(r);
}
}
private void sync(List<Runnable> r) {
for(Runnable i : new KList<>(r))
{
i.run();
}
}
public void sync(Runnable... r) {
for (Runnable i : r) {
i.run();
@@ -126,6 +127,12 @@ public class MultiBurst {
return burst(16);
}
public BurstExecutor burst(boolean multicore) {
BurstExecutor b = burst();
b.setMulticore(multicore);
return b;
}
public <T> Future<T> lazySubmit(Callable<T> o) {
return getService().submit(o);
}
@@ -138,64 +145,15 @@ public class MultiBurst {
return getService().submit(o);
}
public CompletableFuture<?> complete(Runnable o) {
return CompletableFuture.runAsync(o, getService());
public Future<?> complete(Runnable o) {
return getService().submit(o);
}
public <T> CompletableFuture<T> completeValue(Supplier<T> o) {
return CompletableFuture.supplyAsync(o, getService());
public <T> Future<T> completeValue(Callable<T> o) {
return getService().submit(o);
}
public void shutdownNow() {
Iris.debug("Shutting down MultiBurst Pool " + heartbeat.getName() + ".");
heartbeat.interrupt();
if (service != null) {
service.shutdownNow().forEach(Runnable::run);
}
}
public void shutdown() {
Iris.debug("Shutting down MultiBurst Pool " + heartbeat.getName() + ".");
heartbeat.interrupt();
if (service != null) {
service.shutdown();
}
}
public void shutdownLater() {
if (service != null) {
try
{
service.submit(() -> {
J.sleep(3000);
Iris.debug("Shutting down MultiBurst Pool " + heartbeat.getName() + ".");
if (service != null) {
service.shutdown();
}
});
heartbeat.interrupt();
}
catch(Throwable e)
{
Iris.debug("Shutting down MultiBurst Pool " + heartbeat.getName() + ".");
if (service != null) {
service.shutdown();
}
heartbeat.interrupt();
}
}
}
public void shutdownAndAwait() {
Iris.debug("Shutting down MultiBurst Pool " + heartbeat.getName() + ".");
heartbeat.interrupt();
public void close() {
if (service != null) {
service.shutdown();
try {

View File

@@ -311,6 +311,9 @@ public class VolmitSender implements CommandSender {
}
}
public void sendMessageBasic(String message) {
s.sendMessage(C.translateAlternateColorCodes('&', getTag() + message));
}
public void sendMessageRaw(String message) {
if (message.contains("<NOMINI>")) {

View File

@@ -129,7 +129,6 @@ public class J {
try {
r.run();
} catch (Throwable e) {
Iris.reportError(e);
return e;
}

View File

@@ -0,0 +1,42 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2021 Arcane Arts (Volmit Software)
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.volmit.iris.util.scheduling.jobs;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.parallel.BurstExecutor;
import com.volmit.iris.util.parallel.MultiBurst;
public abstract class ParallelQueueJob<T> extends QueueJob<T> {
@Override
public void execute() {
while (queue.isNotEmpty()) {
BurstExecutor b = MultiBurst.burst.burst(queue.size());
KList<T> q = queue.copy();
queue.clear();
for(T i : q)
{
b.queue(() -> {
execute(i);
completeWork();
});
}
b.complete();
}
}
}

View File

@@ -18,27 +18,32 @@
package com.volmit.iris.util.scheduling.jobs;
import com.sun.jna.platform.unix.X11;
import com.volmit.iris.util.collection.KList;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class QueueJob<T> implements Job {
private final KList<T> queue;
private int totalWork;
private int completed;
final KList<T> queue;
protected int totalWork;
private AtomicInteger completed;
public QueueJob() {
totalWork = 0;
completed = 0;
completed = new AtomicInteger(0);
queue = new KList<>();
}
public void queue(T t) {
public QueueJob queue(T t) {
queue.add(t);
totalWork++;
return this;
}
public void queue(KList<T> f) {
public QueueJob queue(KList<T> f) {
queue.addAll(f);
totalWork += f.size();
return this;
}
public abstract void execute(T t);
@@ -54,7 +59,7 @@ public abstract class QueueJob<T> implements Job {
@Override
public void completeWork() {
completed++;
completed.incrementAndGet();
}
@Override
@@ -64,6 +69,6 @@ public abstract class QueueJob<T> implements Job {
@Override
public int getWorkCompleted() {
return completed;
return completed.get();
}
}