MC Utils
== AT == public net.minecraft.server.level.ServerChunkCache mainThread public net.minecraft.server.level.ServerLevel chunkSource public org.bukkit.craftbukkit.inventory.CraftItemStack handle public net.minecraft.server.level.ChunkMap getVisibleChunkIfPresent(J)Lnet/minecraft/server/level/ChunkHolder; public net.minecraft.server.level.ServerChunkCache mainThreadProcessor public net.minecraft.server.level.ServerChunkCache$MainThreadExecutor public net.minecraft.world.level.chunk.LevelChunkSection states
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.util.Priority;
|
||||
import ca.spottedleaf.moonrise.common.PlatformHooks;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraft.server.level.ChunkHolder;
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import org.slf4j.Logger;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class ChunkSystem {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final net.minecraft.world.level.chunk.status.ChunkStep FULL_CHUNK_STEP = net.minecraft.world.level.chunk.status.ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL);
|
||||
|
||||
private static int getDistance(final ChunkStatus status) {
|
||||
return FULL_CHUNK_STEP.getAccumulatedRadiusOf(status);
|
||||
}
|
||||
|
||||
public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) {
|
||||
scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL);
|
||||
}
|
||||
|
||||
public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) {
|
||||
level.chunkSource.mainThreadProcessor.execute(run);
|
||||
}
|
||||
|
||||
public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
|
||||
final ChunkStatus toStatus, final boolean addTicket, final Priority priority,
|
||||
final Consumer<ChunkAccess> onComplete) {
|
||||
if (gen) {
|
||||
scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
|
||||
return;
|
||||
}
|
||||
scheduleChunkLoad(level, chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> {
|
||||
if (chunk == null) {
|
||||
if (onComplete != null) {
|
||||
onComplete.accept(null);
|
||||
}
|
||||
} else {
|
||||
if (chunk.getPersistedStatus().isOrAfter(toStatus)) {
|
||||
scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
|
||||
} else {
|
||||
if (onComplete != null) {
|
||||
onComplete.accept(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static final net.minecraft.server.level.TicketType<Long> CHUNK_LOAD = net.minecraft.server.level.TicketType.create("chunk_load", Long::compareTo);
|
||||
|
||||
private static long chunkLoadCounter = 0L;
|
||||
public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
|
||||
final boolean addTicket, final Priority priority, final Consumer<ChunkAccess> onComplete) {
|
||||
if (!org.bukkit.Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) {
|
||||
scheduleChunkTask(level, chunkX, chunkZ, () -> {
|
||||
scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
|
||||
}, priority);
|
||||
return;
|
||||
}
|
||||
|
||||
final int minLevel = 33 + getDistance(toStatus);
|
||||
final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
|
||||
final net.minecraft.world.level.ChunkPos chunkPos = new net.minecraft.world.level.ChunkPos(chunkX, chunkZ);
|
||||
|
||||
if (addTicket) {
|
||||
level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
|
||||
}
|
||||
level.chunkSource.runDistanceManagerUpdates();
|
||||
|
||||
final Consumer<ChunkAccess> loadCallback = (final ChunkAccess chunk) -> {
|
||||
try {
|
||||
if (onComplete != null) {
|
||||
onComplete.accept(chunk);
|
||||
}
|
||||
} catch (final Throwable thr) {
|
||||
LOGGER.error("Exception handling chunk load callback", thr);
|
||||
com.destroystokyo.paper.util.SneakyThrow.sneaky(thr);
|
||||
} finally {
|
||||
if (addTicket) {
|
||||
level.chunkSource.addTicketAtLevel(net.minecraft.server.level.TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
|
||||
level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
||||
|
||||
if (holder == null || holder.getTicketLevel() > minLevel) {
|
||||
loadCallback.accept(null);
|
||||
return;
|
||||
}
|
||||
|
||||
final java.util.concurrent.CompletableFuture<net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.ChunkAccess>> loadFuture = holder.scheduleChunkGenerationTask(toStatus, level.chunkSource.chunkMap);
|
||||
|
||||
if (loadFuture.isDone()) {
|
||||
loadCallback.accept(loadFuture.join().orElse(null));
|
||||
return;
|
||||
}
|
||||
|
||||
loadFuture.whenCompleteAsync((final net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.ChunkAccess> result, final Throwable thr) -> {
|
||||
if (thr != null) {
|
||||
loadCallback.accept(null);
|
||||
return;
|
||||
}
|
||||
loadCallback.accept(result.orElse(null));
|
||||
}, (final Runnable r) -> {
|
||||
scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
|
||||
});
|
||||
}
|
||||
|
||||
public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
|
||||
final FullChunkStatus toStatus, final boolean addTicket,
|
||||
final Priority priority, final Consumer<LevelChunk> onComplete) {
|
||||
// This method goes unused until the chunk system rewrite
|
||||
if (toStatus == FullChunkStatus.INACCESSIBLE) {
|
||||
throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status");
|
||||
}
|
||||
|
||||
if (!org.bukkit.Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) {
|
||||
scheduleChunkTask(level, chunkX, chunkZ, () -> {
|
||||
scheduleTickingState(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
|
||||
}, priority);
|
||||
return;
|
||||
}
|
||||
|
||||
final int minLevel = 33 - (toStatus.ordinal() - 1);
|
||||
final int radius = toStatus.ordinal() - 1;
|
||||
final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
|
||||
final net.minecraft.world.level.ChunkPos chunkPos = new net.minecraft.world.level.ChunkPos(chunkX, chunkZ);
|
||||
|
||||
if (addTicket) {
|
||||
level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
|
||||
}
|
||||
level.chunkSource.runDistanceManagerUpdates();
|
||||
|
||||
final Consumer<LevelChunk> loadCallback = (final LevelChunk chunk) -> {
|
||||
try {
|
||||
if (onComplete != null) {
|
||||
onComplete.accept(chunk);
|
||||
}
|
||||
} catch (final Throwable thr) {
|
||||
LOGGER.error("Exception handling chunk load callback", thr);
|
||||
com.destroystokyo.paper.util.SneakyThrow.sneaky(thr);
|
||||
} finally {
|
||||
if (addTicket) {
|
||||
level.chunkSource.addTicketAtLevel(net.minecraft.server.level.TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
|
||||
level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
||||
|
||||
if (holder == null || holder.getTicketLevel() > minLevel) {
|
||||
loadCallback.accept(null);
|
||||
return;
|
||||
}
|
||||
|
||||
final java.util.concurrent.CompletableFuture<net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.LevelChunk>> tickingState;
|
||||
switch (toStatus) {
|
||||
case FULL: {
|
||||
tickingState = holder.getFullChunkFuture();
|
||||
break;
|
||||
}
|
||||
case BLOCK_TICKING: {
|
||||
tickingState = holder.getTickingChunkFuture();
|
||||
break;
|
||||
}
|
||||
case ENTITY_TICKING: {
|
||||
tickingState = holder.getEntityTickingChunkFuture();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalStateException("Cannot reach here");
|
||||
}
|
||||
}
|
||||
|
||||
if (tickingState.isDone()) {
|
||||
loadCallback.accept(tickingState.join().orElse(null));
|
||||
return;
|
||||
}
|
||||
|
||||
tickingState.whenCompleteAsync((final net.minecraft.server.level.ChunkResult<net.minecraft.world.level.chunk.LevelChunk> result, final Throwable thr) -> {
|
||||
if (thr != null) {
|
||||
loadCallback.accept(null);
|
||||
return;
|
||||
}
|
||||
loadCallback.accept(result.orElse(null));
|
||||
}, (final Runnable r) -> {
|
||||
scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
|
||||
});
|
||||
}
|
||||
|
||||
public static List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
|
||||
return new java.util.ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values());
|
||||
}
|
||||
|
||||
public static List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
|
||||
return new java.util.ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values());
|
||||
}
|
||||
|
||||
public static int getVisibleChunkHolderCount(final ServerLevel level) {
|
||||
return level.chunkSource.chunkMap.visibleChunkMap.size();
|
||||
}
|
||||
|
||||
public static int getUpdatingChunkHolderCount(final ServerLevel level) {
|
||||
return level.chunkSource.chunkMap.updatingChunkMap.size();
|
||||
}
|
||||
|
||||
public static boolean hasAnyChunkHolders(final ServerLevel level) {
|
||||
return getUpdatingChunkHolderCount(level) != 0;
|
||||
}
|
||||
|
||||
public static boolean screenEntity(final ServerLevel level, final Entity entity, final boolean fromDisk, final boolean event) {
|
||||
if (!PlatformHooks.get().screenEntity(level, entity, fromDisk, event)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) {
|
||||
|
||||
}
|
||||
|
||||
public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
|
||||
|
||||
}
|
||||
|
||||
public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) {
|
||||
|
||||
}
|
||||
|
||||
public static void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) {
|
||||
|
||||
}
|
||||
|
||||
public static void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) {
|
||||
|
||||
}
|
||||
|
||||
public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) {
|
||||
|
||||
}
|
||||
|
||||
public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
|
||||
|
||||
}
|
||||
|
||||
public static void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
|
||||
|
||||
}
|
||||
|
||||
public static ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) {
|
||||
return level.chunkSource.chunkMap.getUnloadingChunkHolder(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
public static int getSendViewDistance(final ServerPlayer player) {
|
||||
return getViewDistance(player);
|
||||
}
|
||||
|
||||
public static int getViewDistance(final ServerPlayer player) {
|
||||
final ServerLevel level = player.serverLevel();
|
||||
if (level == null) {
|
||||
return org.bukkit.Bukkit.getViewDistance();
|
||||
}
|
||||
return level.chunkSource.chunkMap.serverViewDistance;
|
||||
}
|
||||
|
||||
public static int getTickViewDistance(final ServerPlayer player) {
|
||||
final ServerLevel level = player.serverLevel();
|
||||
if (level == null) {
|
||||
return org.bukkit.Bukkit.getSimulationDistance();
|
||||
}
|
||||
return level.chunkSource.chunkMap.distanceManager.simulationDistance;
|
||||
}
|
||||
|
||||
private ChunkSystem() {}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public final class CoordinateUtils {
|
||||
|
||||
// the chunk keys are compatible with vanilla
|
||||
|
||||
public static long getChunkKey(final BlockPos pos) {
|
||||
return ((long)(pos.getZ() >> 4) << 32) | ((pos.getX() >> 4) & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
public static long getChunkKey(final Entity entity) {
|
||||
return ((Mth.lfloor(entity.getZ()) >> 4) << 32) | ((Mth.lfloor(entity.getX()) >> 4) & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
public static long getChunkKey(final ChunkPos pos) {
|
||||
return ((long)pos.z << 32) | (pos.x & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
public static long getChunkKey(final SectionPos pos) {
|
||||
return ((long)pos.getZ() << 32) | (pos.getX() & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
public static long getChunkKey(final int x, final int z) {
|
||||
return ((long)z << 32) | (x & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
public static int getChunkX(final long chunkKey) {
|
||||
return (int)chunkKey;
|
||||
}
|
||||
|
||||
public static int getChunkZ(final long chunkKey) {
|
||||
return (int)(chunkKey >>> 32);
|
||||
}
|
||||
|
||||
public static int getChunkCoordinate(final double blockCoordinate) {
|
||||
return Mth.floor(blockCoordinate) >> 4;
|
||||
}
|
||||
|
||||
// the section keys are compatible with vanilla's
|
||||
|
||||
static final int SECTION_X_BITS = 22;
|
||||
static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1;
|
||||
static final int SECTION_Y_BITS = 20;
|
||||
static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1;
|
||||
static final int SECTION_Z_BITS = 22;
|
||||
static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1;
|
||||
// format is y,z,x (in order of LSB to MSB)
|
||||
static final int SECTION_Y_SHIFT = 0;
|
||||
static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS;
|
||||
static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS;
|
||||
static final int SECTION_TO_BLOCK_SHIFT = 4;
|
||||
|
||||
public static long getChunkSectionKey(final int x, final int y, final int z) {
|
||||
return ((x & SECTION_X_MASK) << SECTION_X_SHIFT)
|
||||
| ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT)
|
||||
| ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT);
|
||||
}
|
||||
|
||||
public static long getChunkSectionKey(final SectionPos pos) {
|
||||
return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT)
|
||||
| ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT)
|
||||
| ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT);
|
||||
}
|
||||
|
||||
public static long getChunkSectionKey(final ChunkPos pos, final int y) {
|
||||
return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT)
|
||||
| ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT)
|
||||
| ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT);
|
||||
}
|
||||
|
||||
public static long getChunkSectionKey(final BlockPos pos) {
|
||||
return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) |
|
||||
((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) |
|
||||
(((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT));
|
||||
}
|
||||
|
||||
public static long getChunkSectionKey(final Entity entity) {
|
||||
return ((Mth.lfloor(entity.getX()) << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) |
|
||||
((Mth.lfloor(entity.getY()) >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) |
|
||||
((Mth.lfloor(entity.getZ()) << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT));
|
||||
}
|
||||
|
||||
public static int getChunkSectionX(final long key) {
|
||||
return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS));
|
||||
}
|
||||
|
||||
public static int getChunkSectionY(final long key) {
|
||||
return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS));
|
||||
}
|
||||
|
||||
public static int getChunkSectionZ(final long key) {
|
||||
return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS));
|
||||
}
|
||||
|
||||
public static int getBlockX(final Vec3 pos) {
|
||||
return Mth.floor(pos.x);
|
||||
}
|
||||
|
||||
public static int getBlockY(final Vec3 pos) {
|
||||
return Mth.floor(pos.y);
|
||||
}
|
||||
|
||||
public static int getBlockZ(final Vec3 pos) {
|
||||
return Mth.floor(pos.z);
|
||||
}
|
||||
|
||||
public static int getChunkX(final Vec3 pos) {
|
||||
return Mth.floor(pos.x) >> 4;
|
||||
}
|
||||
|
||||
public static int getChunkY(final Vec3 pos) {
|
||||
return Mth.floor(pos.y) >> 4;
|
||||
}
|
||||
|
||||
public static int getChunkZ(final Vec3 pos) {
|
||||
return Mth.floor(pos.z) >> 4;
|
||||
}
|
||||
|
||||
private CoordinateUtils() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class FlatBitsetUtil {
|
||||
|
||||
private static final int LOG2_LONG = 6;
|
||||
private static final long ALL_SET = -1L;
|
||||
private static final int BITS_PER_LONG = Long.SIZE;
|
||||
|
||||
// from inclusive
|
||||
// to exclusive
|
||||
public static int firstSet(final long[] bitset, final int from, final int to) {
|
||||
if ((from | to | (to - from)) < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
int bitsetIdx = from >>> LOG2_LONG;
|
||||
int bitIdx = from & ~(BITS_PER_LONG - 1);
|
||||
|
||||
long tmp = bitset[bitsetIdx] & (ALL_SET << from);
|
||||
for (;;) {
|
||||
if (tmp != 0L) {
|
||||
final int ret = bitIdx | Long.numberOfTrailingZeros(tmp);
|
||||
return ret >= to ? -1 : ret;
|
||||
}
|
||||
|
||||
bitIdx += BITS_PER_LONG;
|
||||
|
||||
if (bitIdx >= to) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
tmp = bitset[++bitsetIdx];
|
||||
}
|
||||
}
|
||||
|
||||
// from inclusive
|
||||
// to exclusive
|
||||
public static int firstClear(final long[] bitset, final int from, final int to) {
|
||||
if ((from | to | (to - from)) < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
// like firstSet, but invert the bitset
|
||||
|
||||
int bitsetIdx = from >>> LOG2_LONG;
|
||||
int bitIdx = from & ~(BITS_PER_LONG - 1);
|
||||
|
||||
long tmp = (~bitset[bitsetIdx]) & (ALL_SET << from);
|
||||
for (;;) {
|
||||
if (tmp != 0L) {
|
||||
final int ret = bitIdx | Long.numberOfTrailingZeros(tmp);
|
||||
return ret >= to ? -1 : ret;
|
||||
}
|
||||
|
||||
bitIdx += BITS_PER_LONG;
|
||||
|
||||
if (bitIdx >= to) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
tmp = ~bitset[++bitsetIdx];
|
||||
}
|
||||
}
|
||||
|
||||
// from inclusive
|
||||
// to exclusive
|
||||
public static void clearRange(final long[] bitset, final int from, int to) {
|
||||
if ((from | to | (to - from)) < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
if (from == to) {
|
||||
return;
|
||||
}
|
||||
|
||||
--to;
|
||||
|
||||
final int fromBitsetIdx = from >>> LOG2_LONG;
|
||||
final int toBitsetIdx = to >>> LOG2_LONG;
|
||||
|
||||
final long keepFirst = ~(ALL_SET << from);
|
||||
final long keepLast = ~(ALL_SET >>> ((BITS_PER_LONG - 1) ^ to));
|
||||
|
||||
Objects.checkFromToIndex(fromBitsetIdx, toBitsetIdx, bitset.length);
|
||||
|
||||
if (fromBitsetIdx == toBitsetIdx) {
|
||||
// special case: need to keep both first and last
|
||||
bitset[fromBitsetIdx] &= (keepFirst | keepLast);
|
||||
} else {
|
||||
bitset[fromBitsetIdx] &= keepFirst;
|
||||
|
||||
for (int i = fromBitsetIdx + 1; i < toBitsetIdx; ++i) {
|
||||
bitset[i] = 0L;
|
||||
}
|
||||
|
||||
bitset[toBitsetIdx] &= keepLast;
|
||||
}
|
||||
}
|
||||
|
||||
// from inclusive
|
||||
// to exclusive
|
||||
public static boolean isRangeSet(final long[] bitset, final int from, final int to) {
|
||||
return firstClear(bitset, from, to) == -1;
|
||||
}
|
||||
|
||||
|
||||
private FlatBitsetUtil() {}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.internal.Streams;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public final class JsonUtil {
|
||||
|
||||
public static void writeJson(final JsonElement element, final File file) throws IOException {
|
||||
final StringWriter stringWriter = new StringWriter();
|
||||
final JsonWriter jsonWriter = new JsonWriter(stringWriter);
|
||||
jsonWriter.setIndent(" ");
|
||||
jsonWriter.setLenient(false);
|
||||
Streams.write(element, jsonWriter);
|
||||
|
||||
final String jsonString = stringWriter.toString();
|
||||
|
||||
final File parent = file.getParentFile();
|
||||
if (parent != null) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
file.createNewFile();
|
||||
try (final PrintStream out = new PrintStream(new FileOutputStream(file), false, StandardCharsets.UTF_8)) {
|
||||
out.print(jsonString);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
public final class MixinWorkarounds {
|
||||
|
||||
// mixins tries to find the owner of the clone() method, which doesn't exist and NPEs
|
||||
// https://github.com/FabricMC/Mixin/pull/147
|
||||
public static long[] clone(final long[] values) {
|
||||
return values.clone();
|
||||
}
|
||||
|
||||
public static byte[] clone(final byte[] values) {
|
||||
return values.clone();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool;
|
||||
import ca.spottedleaf.moonrise.common.PlatformHooks;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class MoonriseCommon {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getClassLogger();
|
||||
|
||||
public static final PrioritisedThreadPool WORKER_POOL = new PrioritisedThreadPool(
|
||||
new Consumer<>() {
|
||||
private final AtomicInteger idGenerator = new AtomicInteger();
|
||||
|
||||
@Override
|
||||
public void accept(Thread thread) {
|
||||
thread.setDaemon(true);
|
||||
thread.setName(PlatformHooks.get().getBrand() + " Common Worker #" + this.idGenerator.getAndIncrement());
|
||||
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(final Thread thread, final Throwable throwable) {
|
||||
LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
public static final long WORKER_QUEUE_HOLD_TIME = (long)(20.0e6); // 20ms
|
||||
public static final int CLIENT_DIVISION = 0;
|
||||
public static final PrioritisedThreadPool.ExecutorGroup RENDER_EXECUTOR_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(CLIENT_DIVISION, 0);
|
||||
public static final int SERVER_DIVISION = 1;
|
||||
public static final PrioritisedThreadPool.ExecutorGroup PARALLEL_GEN_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0);
|
||||
public static final PrioritisedThreadPool.ExecutorGroup RADIUS_AWARE_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0);
|
||||
public static final PrioritisedThreadPool.ExecutorGroup LOAD_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0);
|
||||
|
||||
public static void adjustWorkerThreads(final int configWorkerThreads, final int configIoThreads) {
|
||||
int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2;
|
||||
if (defaultWorkerThreads <= 4) {
|
||||
defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2;
|
||||
} else {
|
||||
defaultWorkerThreads = defaultWorkerThreads / 2;
|
||||
}
|
||||
defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads));
|
||||
|
||||
int workerThreads = configWorkerThreads;
|
||||
|
||||
if (workerThreads <= 0) {
|
||||
workerThreads = defaultWorkerThreads;
|
||||
}
|
||||
|
||||
final int ioThreads = Math.max(1, configIoThreads);
|
||||
|
||||
WORKER_POOL.adjustThreadCount(workerThreads);
|
||||
IO_POOL.adjustThreadCount(ioThreads);
|
||||
|
||||
LOGGER.info(PlatformHooks.get().getBrand() + " is using " + workerThreads + " worker threads, " + ioThreads + " I/O threads");
|
||||
}
|
||||
|
||||
public static final PrioritisedThreadPool IO_POOL = new PrioritisedThreadPool(
|
||||
new Consumer<>() {
|
||||
private final AtomicInteger idGenerator = new AtomicInteger();
|
||||
|
||||
@Override
|
||||
public void accept(final Thread thread) {
|
||||
thread.setDaemon(true);
|
||||
thread.setName(PlatformHooks.get().getBrand() + " I/O Worker #" + this.idGenerator.getAndIncrement());
|
||||
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(final Thread thread, final Throwable throwable) {
|
||||
LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
public static final long IO_QUEUE_HOLD_TIME = (long)(100.0e6); // 100ms
|
||||
public static final PrioritisedThreadPool.ExecutorGroup CLIENT_PROFILER_IO_GROUP = IO_POOL.createExecutorGroup(CLIENT_DIVISION, 0);
|
||||
public static final PrioritisedThreadPool.ExecutorGroup SERVER_REGION_IO_GROUP = IO_POOL.createExecutorGroup(SERVER_DIVISION, 0);
|
||||
|
||||
public static void haltExecutors() {
|
||||
MoonriseCommon.WORKER_POOL.shutdown(false);
|
||||
LOGGER.info("Awaiting termination of worker pool for up to 60s...");
|
||||
if (!MoonriseCommon.WORKER_POOL.join(TimeUnit.SECONDS.toMillis(60L))) {
|
||||
LOGGER.error("Worker pool did not shut down in time!");
|
||||
MoonriseCommon.WORKER_POOL.halt(false);
|
||||
}
|
||||
|
||||
MoonriseCommon.IO_POOL.shutdown(false);
|
||||
LOGGER.info("Awaiting termination of I/O pool for up to 60s...");
|
||||
if (!MoonriseCommon.IO_POOL.join(TimeUnit.SECONDS.toMillis(60L))) {
|
||||
LOGGER.error("I/O pool did not shut down in time!");
|
||||
MoonriseCommon.IO_POOL.halt(false);
|
||||
}
|
||||
}
|
||||
|
||||
private MoonriseCommon() {}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.PlatformHooks;
|
||||
|
||||
public final class MoonriseConstants {
|
||||
|
||||
public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32);
|
||||
|
||||
private MoonriseConstants() {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.levelgen.BitRandomSource;
|
||||
import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian;
|
||||
import net.minecraft.world.level.levelgen.PositionalRandomFactory;
|
||||
|
||||
/**
|
||||
* Avoid costly CAS of superclass + division in nextInt
|
||||
*/
|
||||
public final class SimpleThreadUnsafeRandom implements BitRandomSource {
|
||||
|
||||
private static final long MULTIPLIER = 25214903917L;
|
||||
private static final long ADDEND = 11L;
|
||||
private static final int BITS = 48;
|
||||
private static final long MASK = (1L << BITS) - 1L;
|
||||
|
||||
private long value;
|
||||
private final MarsagliaPolarGaussian gaussianSource = new MarsagliaPolarGaussian(this);
|
||||
|
||||
public SimpleThreadUnsafeRandom(final long seed) {
|
||||
this.setSeed(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSeed(final long seed) {
|
||||
this.value = (seed ^ MULTIPLIER) & MASK;
|
||||
this.gaussianSource.reset();
|
||||
}
|
||||
|
||||
private long advanceSeed() {
|
||||
return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int next(final int bits) {
|
||||
return (int)(this.advanceSeed() >>> (BITS - bits));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextInt() {
|
||||
final long seed = this.advanceSeed();
|
||||
return (int)(seed >>> (BITS - Integer.SIZE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextInt(final int bound) {
|
||||
if (bound <= 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
// https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
|
||||
final long value = this.advanceSeed() >>> (BITS - Integer.SIZE);
|
||||
return (int)((value * (long)bound) >>> Integer.SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double nextGaussian() {
|
||||
return this.gaussianSource.nextGaussian();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource fork() {
|
||||
return new SimpleThreadUnsafeRandom(this.nextLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PositionalRandomFactory forkPositional() {
|
||||
return new SimpleRandomPositionalFactory(this.nextLong());
|
||||
}
|
||||
|
||||
public static final class SimpleRandomPositionalFactory implements PositionalRandomFactory {
|
||||
|
||||
private final long seed;
|
||||
|
||||
public SimpleRandomPositionalFactory(final long seed) {
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
public long getSeed() {
|
||||
return this.seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource fromHashOf(final String string) {
|
||||
return new SimpleThreadUnsafeRandom((long)string.hashCode() ^ this.seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource fromSeed(final long seed) {
|
||||
return new SimpleThreadUnsafeRandom(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource at(final int x, final int y, final int z) {
|
||||
return new SimpleThreadUnsafeRandom(Mth.getSeed(x, y, z) ^ this.seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parityConfigString(final StringBuilder stringBuilder) {
|
||||
stringBuilder.append("SimpleRandomPositionalFactory{").append(this.seed).append('}');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.levelgen.BitRandomSource;
|
||||
import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian;
|
||||
import net.minecraft.world.level.levelgen.PositionalRandomFactory;
|
||||
|
||||
/**
|
||||
* Avoid costly CAS of superclass
|
||||
*/
|
||||
public final class ThreadUnsafeRandom implements BitRandomSource {
|
||||
|
||||
private static final long MULTIPLIER = 25214903917L;
|
||||
private static final long ADDEND = 11L;
|
||||
private static final int BITS = 48;
|
||||
private static final long MASK = (1L << BITS) - 1L;
|
||||
|
||||
private long value;
|
||||
private final MarsagliaPolarGaussian gaussianSource = new MarsagliaPolarGaussian(this);
|
||||
|
||||
public ThreadUnsafeRandom(final long seed) {
|
||||
this.setSeed(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSeed(final long seed) {
|
||||
this.value = (seed ^ MULTIPLIER) & MASK;
|
||||
this.gaussianSource.reset();
|
||||
}
|
||||
|
||||
private long advanceSeed() {
|
||||
return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int next(final int bits) {
|
||||
return (int)(this.advanceSeed() >>> (BITS - bits));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextInt() {
|
||||
final long seed = this.advanceSeed();
|
||||
return (int)(seed >>> (BITS - Integer.SIZE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double nextGaussian() {
|
||||
return this.gaussianSource.nextGaussian();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource fork() {
|
||||
return new ThreadUnsafeRandom(this.nextLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PositionalRandomFactory forkPositional() {
|
||||
return new ThreadUnsafeRandomPositionalFactory(this.nextLong());
|
||||
}
|
||||
|
||||
public static final class ThreadUnsafeRandomPositionalFactory implements PositionalRandomFactory {
|
||||
|
||||
private final long seed;
|
||||
|
||||
public ThreadUnsafeRandomPositionalFactory(final long seed) {
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
public long getSeed() {
|
||||
return this.seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource fromHashOf(final String string) {
|
||||
return new ThreadUnsafeRandom((long)string.hashCode() ^ this.seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource fromSeed(final long seed) {
|
||||
return new ThreadUnsafeRandom(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource at(final int x, final int y, final int z) {
|
||||
return new ThreadUnsafeRandom(Mth.getSeed(x, y, z) ^ this.seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parityConfigString(final StringBuilder stringBuilder) {
|
||||
stringBuilder.append("ThreadUnsafeRandomPositionalFactory{").append(this.seed).append('}');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class TickThread extends Thread {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TickThread.class);
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public static void ensureTickThread(final String reason) {
|
||||
if (!isTickThread()) {
|
||||
LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ensureTickThread(final Level world, final BlockPos pos, final String reason) {
|
||||
if (!isTickThreadFor(world, pos)) {
|
||||
LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ensureTickThread(final Level world, final ChunkPos pos, final String reason) {
|
||||
if (!isTickThreadFor(world, pos)) {
|
||||
LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ensureTickThread(final Level world, final int chunkX, final int chunkZ, final String reason) {
|
||||
if (!isTickThreadFor(world, chunkX, chunkZ)) {
|
||||
LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ensureTickThread(final Entity entity, final String reason) {
|
||||
if (!isTickThreadFor(entity)) {
|
||||
LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ensureTickThread(final Level world, final AABB aabb, final String reason) {
|
||||
if (!isTickThreadFor(world, aabb)) {
|
||||
LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ensureTickThread(final Level world, final double blockX, final double blockZ, final String reason) {
|
||||
if (!isTickThreadFor(world, blockX, blockZ)) {
|
||||
LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
}
|
||||
|
||||
public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */
|
||||
|
||||
private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
|
||||
|
||||
public TickThread(final String name) {
|
||||
this(null, name);
|
||||
}
|
||||
|
||||
public TickThread(final Runnable run, final String name) {
|
||||
this(null, run, name);
|
||||
}
|
||||
|
||||
public TickThread(final ThreadGroup group, final Runnable run, final String name) {
|
||||
this(group, run, name, ID_GENERATOR.incrementAndGet());
|
||||
}
|
||||
|
||||
private TickThread(final ThreadGroup group, final Runnable run, final String name, final int id) {
|
||||
super(group, run, name);
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static TickThread getCurrentTickThread() {
|
||||
return (TickThread)Thread.currentThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThread() {
|
||||
return Thread.currentThread() instanceof TickThread;
|
||||
}
|
||||
|
||||
public static boolean isShutdownThread() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final Level world, final BlockPos pos) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final Level world, final ChunkPos pos) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final Level world, final Vec3 pos) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final Level world, final AABB aabb) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final Level world, final double blockX, final double blockZ) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final Level world, final Vec3 position, final Vec3 deltaMovement, final int buffer) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final Level world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ, final int radius) {
|
||||
return isTickThread();
|
||||
}
|
||||
|
||||
public static boolean isTickThreadFor(final Entity entity) {
|
||||
return isTickThread();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
|
||||
public final class WorldUtil {
|
||||
|
||||
// min, max are inclusive
|
||||
|
||||
public static int getMaxSection(final LevelHeightAccessor world) {
|
||||
return world.getMaxSectionY();
|
||||
}
|
||||
|
||||
public static int getMaxSection(final Level world) {
|
||||
return world.getMaxSectionY();
|
||||
}
|
||||
|
||||
public static int getMinSection(final LevelHeightAccessor world) {
|
||||
return world.getMinSectionY();
|
||||
}
|
||||
|
||||
public static int getMinSection(final Level world) {
|
||||
return world.getMinSectionY();
|
||||
}
|
||||
|
||||
public static int getMaxLightSection(final LevelHeightAccessor world) {
|
||||
return getMaxSection(world) + 1;
|
||||
}
|
||||
|
||||
public static int getMinLightSection(final LevelHeightAccessor world) {
|
||||
return getMinSection(world) - 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static int getTotalSections(final LevelHeightAccessor world) {
|
||||
return getMaxSection(world) - getMinSection(world) + 1;
|
||||
}
|
||||
|
||||
public static int getTotalLightSections(final LevelHeightAccessor world) {
|
||||
return getMaxLightSection(world) - getMinLightSection(world) + 1;
|
||||
}
|
||||
|
||||
public static int getMinBlockY(final LevelHeightAccessor world) {
|
||||
return getMinSection(world) << 4;
|
||||
}
|
||||
|
||||
public static int getMaxBlockY(final LevelHeightAccessor world) {
|
||||
return (getMaxSection(world) << 4) | 15;
|
||||
}
|
||||
|
||||
public static String getWorldName(final Level world) {
|
||||
if (world == null) {
|
||||
return "null world";
|
||||
}
|
||||
return world.getWorld().getName(); // Paper
|
||||
}
|
||||
|
||||
private WorldUtil() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user