Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,28 +49,54 @@ public static String[] asCode(int... values) {
return IntStream.of(values).mapToObj(Integer::toString).toArray(String[]::new);
}

/**
* Strips a word from a camelCase name, ensuring the word is properly part of the camelCase structure.
* This implementation avoids creating unnecessary intermediate strings by using StringBuilder.
*
* @param name The original name in camelCase
* @param word The word to strip
* @param onlyOnce True to strip only the first occurrence, false to strip all occurrences
* @return The name with the specified word removed
*/
public static String stripWordOfCamelCaseName(String name, String word, boolean onlyOnce) {
String newName = name;
StringBuilder result = new StringBuilder(name.length());
int startIndex = 0;
while (true) {
int baseIndex = newName.indexOf(word, startIndex);
if (baseIndex == -1) {
return newName;
int currentIndex = 0;

while ((currentIndex = name.indexOf(word, startIndex)) != -1) {
// Check if this occurrence meets our criteria
boolean validOccurrence = true;

if (currentIndex > 0 && !Character.isLowerCase(name.charAt(currentIndex - 1))) {
validOccurrence = false;
}

if ((baseIndex > 0 && !Character.isLowerCase(newName.charAt(baseIndex - 1))) ||
(baseIndex + word.length() < newName.length() && !Character.isUpperCase(newName.charAt(baseIndex + word.length())))) {
startIndex = baseIndex + word.length();
continue;
if (currentIndex + word.length() < name.length() &&
!Character.isUpperCase(name.charAt(currentIndex + word.length()))) {
validOccurrence = false;
}

newName = newName.substring(0, baseIndex) + newName.substring(baseIndex + word.length());
startIndex = baseIndex;
if (onlyOnce) {
break;
if (validOccurrence) {
// Append text before this occurrence
result.append(name, startIndex, currentIndex);
// Skip the word
startIndex = currentIndex + word.length();

if (onlyOnce) {
break;
}
} else {
// Move past this occurrence
startIndex = currentIndex + 1;
}
}
return newName;

// Append any remaining text
if (startIndex < name.length()) {
result.append(name, startIndex, name.length());
}

return result.toString();
}

public static final Comparator<String> ALPHABETIC_KEY_ORDER = alphabeticKeyOrder(path -> path);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package ca.spottedleaf.moonrise.common.misc;

/**
* Rate limiter that allocates and controls resource consumption over time.
* Uses a token bucket algorithm to manage allocated resources with time-based replenishment.
*/
public final class AllocatingRateLimiter {

// max difference granularity in ns
Expand All @@ -13,55 +17,103 @@ public final class AllocatingRateLimiter {
private double takeCarry = 0.0;
private long lastTakeUpdate;

/**
* Creates a new rate limiter with specified maximum time granularity.
*
* @param maxGranularity Maximum time difference to consider in nanoseconds
*/
public AllocatingRateLimiter(final long maxGranularity) {
this.maxGranularity = maxGranularity;
}

/**
* Resets the rate limiter state to initial values.
*
* @param time Current time in nanoseconds
*/
public void reset(final long time) {
this.allocation = 0.0;
this.lastAllocationUpdate = time;
this.takeCarry = 0.0;
this.lastTakeUpdate = time;
}

// rate in units/s, and time in ns
/**
* Updates the allocation based on elapsed time and rate.
* Skips updates for very small or negative time differences to prevent numerical instability.
*
* @param time Current time in nanoseconds
* @param rate Rate of allocation replenishment in units per second
* @param maxAllocation Maximum allocation allowed
*/
public void tickAllocation(final long time, final double rate, final double maxAllocation) {
final long diff = Math.min(this.maxGranularity, time - this.lastAllocationUpdate);
final long timeDiff = time - this.lastAllocationUpdate;

// Skip updates for very small or negative time differences to prevent numerical instability
if (timeDiff <= 0) {
return;
}

final long diff = Math.min(this.maxGranularity, timeDiff);
this.lastAllocationUpdate = time;

this.allocation = Math.min(maxAllocation - this.takeCarry, this.allocation + rate * (diff*1.0E-9D));
this.allocation = Math.min(maxAllocation - this.takeCarry, this.allocation + rate * (diff * 1.0E-9D));
}

/**
* Estimates how much allocation can be taken without actually taking it.
*
* @param time Current time in nanoseconds
* @param rate Rate of allocation replenishment in units per second
* @param maxTake Maximum amount to take
* @return Estimated amount that could be taken
*/
public long previewAllocation(final long time, final double rate, final long maxTake) {
if (maxTake < 1L) {
return 0L;
}

final long diff = Math.min(this.maxGranularity, time - this.lastTakeUpdate);
final long timeDiff = time - this.lastTakeUpdate;
if (timeDiff <= 0) {
// For negative or zero time differences, only use existing allocation
return (long)Math.floor(this.takeCarry + Math.min(Math.min((double)maxTake - this.takeCarry, this.allocation), 0.0));
}

final long diff = Math.min(this.maxGranularity, timeDiff);

// note: abs(takeCarry) <= 1.0
final double take = Math.min(
Math.min((double)maxTake - this.takeCarry, this.allocation),
rate * (diff*1.0E-9)
rate * (diff * 1.0E-9)
);

return (long)Math.floor(this.takeCarry + take);
}

// rate in units/s, and time in ns
/**
* Takes allocation based on elapsed time and rate.
*
* @param time Current time in nanoseconds
* @param rate Rate of allocation replenishment in units per second
* @param maxTake Maximum amount to take
* @return Amount actually taken, as a whole number
*/
public long takeAllocation(final long time, final double rate, final long maxTake) {
if (maxTake < 1L) {
return 0L;
}

double ret = this.takeCarry;
final long diff = Math.min(this.maxGranularity, time - this.lastTakeUpdate);
final long timeDiff = time - this.lastTakeUpdate;

// For negative time differences, only use existing allocation without time-based replenishment
final long diff = timeDiff <= 0 ? 0 : Math.min(this.maxGranularity, timeDiff);
this.lastTakeUpdate = time;

// note: abs(takeCarry) <= 1.0
final double take = Math.min(
Math.min((double)maxTake - this.takeCarry, this.allocation),
rate * (diff*1.0E-9)
rate * (diff * 1.0E-9)
);

ret += take;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,19 @@
import net.minecraft.world.level.levelgen.PositionalRandomFactory;

/**
* Avoid costly CAS of superclass
* A fast, non-thread-safe random number generator optimized for single-threaded usage.
* <p>
* IMPORTANT: This class is NOT thread-safe. Multiple threads accessing the same instance
* will produce unpredictable results and lead to data corruption. Use either:
* - One instance per thread (using ThreadLocal)
* - Synchronization when accessing from multiple threads
* - A thread-safe alternative when shared access is required
* </p>
* <p>
* This implementation uses a linear congruential generator (LCG) for fast random number
* generation with good statistical properties for game mechanics and simulations.
* It trades thread safety for performance in single-threaded contexts.
* </p>
*/
public class ThreadUnsafeRandom implements BitRandomSource { // Paper - replace random

Expand Down Expand Up @@ -44,6 +56,41 @@ public int nextInt() {
return (int)(seed >>> (BITS - Integer.SIZE));
}

/**
* Returns a pseudorandom, uniformly distributed int value between 0 (inclusive)
* and the specified bound (exclusive).
*
* @param bound the upper bound (exclusive) for the random value
* @return a random int in the range [0, bound)
* @throws IllegalArgumentException if bound is not positive
*/
public int nextInt(final int bound) {
if (bound <= 0) {
throw new IllegalArgumentException("bound must be positive");
}

// Fast path for power of 2 bounds
if ((bound & -bound) == bound) {
return (int)(advanceSeed() & (bound - 1));
}

// Rejection sampling for uniform distribution
int r = nextInt();
int m = bound - 1;
if ((bound & m) == 0) {
// Power of two case
r = (int)((bound * (long)r) >> 31);
} else {
// General case
int u = r >>> 1;
while (u + m - (r = u % bound) < 0) {
u = nextInt() >>> 1;
}
}

return r;
}

@Override
public double nextGaussian() {
return this.gaussianSource.nextGaussian();
Expand All @@ -59,6 +106,10 @@ public PositionalRandomFactory forkPositional() {
return new ThreadUnsafeRandomPositionalFactory(this.nextLong());
}

/**
* Factory for creating position-based random sources.
* This allows for consistent random generation based on world positions.
*/
public static final class ThreadUnsafeRandomPositionalFactory implements PositionalRandomFactory {

private final long seed;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.papermc.paper.antixray;

/**
* Utility class for common bit manipulation operations used by BitStorageReader and BitStorageWriter.
* Extracts shared logic to improve maintainability and reduce code duplication.
*/
public final class BitManipulationUtil {

private BitManipulationUtil() {
// Utility class, prevent instantiation
}

/**
* Reads a long value from a byte array at the specified index.
*
* @param buffer The byte array to read from
* @param longInBufferIndex The index to start reading from
* @return The long value read from the buffer, or 0 if the buffer is too short
*/
public static long readLongFromBuffer(byte[] buffer, int longInBufferIndex) {
if (buffer.length <= longInBufferIndex + 7) {
return 0L;
}

return ((((long) buffer[longInBufferIndex]) << 56)
| (((long) buffer[longInBufferIndex + 1] & 0xff) << 48)
| (((long) buffer[longInBufferIndex + 2] & 0xff) << 40)
| (((long) buffer[longInBufferIndex + 3] & 0xff) << 32)
| (((long) buffer[longInBufferIndex + 4] & 0xff) << 24)
| (((long) buffer[longInBufferIndex + 5] & 0xff) << 16)
| (((long) buffer[longInBufferIndex + 6] & 0xff) << 8)
| (((long) buffer[longInBufferIndex + 7] & 0xff)));
}

/**
* Writes a long value to a byte array at the specified index.
*
* @param buffer The byte array to write to
* @param longInBufferIndex The index to start writing at
* @param value The long value to write
* @return True if the write was successful, false if the buffer is too short
*/
public static boolean writeLongToBuffer(byte[] buffer, int longInBufferIndex, long value) {
if (buffer.length <= longInBufferIndex + 7) {
return false;
}

buffer[longInBufferIndex] = (byte) (value >> 56 & 0xff);
buffer[longInBufferIndex + 1] = (byte) (value >> 48 & 0xff);
buffer[longInBufferIndex + 2] = (byte) (value >> 40 & 0xff);
buffer[longInBufferIndex + 3] = (byte) (value >> 32 & 0xff);
buffer[longInBufferIndex + 4] = (byte) (value >> 24 & 0xff);
buffer[longInBufferIndex + 5] = (byte) (value >> 16 & 0xff);
buffer[longInBufferIndex + 6] = (byte) (value >> 8 & 0xff);
buffer[longInBufferIndex + 7] = (byte) (value & 0xff);

return true;
}

/**
* Creates a bit mask for the specified number of bits.
*
* @param bits The number of bits in the mask
* @return A mask with the specified number of least significant bits set to 1
*/
public static long createBitMask(int bits) {
return (1L << bits) - 1L;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ public final class BitStorageReader {

private byte[] buffer;
private int bits;
private int mask;
private long mask; // Changed from int to long to match the utility's return type
private int longInBufferIndex;
private int bitInLongIndex;
private long current;
Expand All @@ -15,7 +15,7 @@ public void setBuffer(byte[] buffer) {

public void setBits(int bits) {
this.bits = bits;
mask = (1 << bits) - 1;
mask = BitManipulationUtil.createBitMask(bits);
}

public void setIndex(int index) {
Expand All @@ -25,16 +25,7 @@ public void setIndex(int index) {
}

private void init() {
if (buffer.length > longInBufferIndex + 7) {
current = ((((long) buffer[longInBufferIndex]) << 56)
| (((long) buffer[longInBufferIndex + 1] & 0xff) << 48)
| (((long) buffer[longInBufferIndex + 2] & 0xff) << 40)
| (((long) buffer[longInBufferIndex + 3] & 0xff) << 32)
| (((long) buffer[longInBufferIndex + 4] & 0xff) << 24)
| (((long) buffer[longInBufferIndex + 5] & 0xff) << 16)
| (((long) buffer[longInBufferIndex + 6] & 0xff) << 8)
| (((long) buffer[longInBufferIndex + 7] & 0xff)));
}
current = BitManipulationUtil.readLongFromBuffer(buffer, longInBufferIndex);
}

public int read() {
Expand All @@ -44,7 +35,7 @@ public int read() {
init();
}

int value = (int) (current >>> bitInLongIndex) & mask;
int value = (int) (current >>> bitInLongIndex) & (int)mask;
bitInLongIndex += bits;
return value;
}
Expand Down
Loading