Skip to content

Commit

Permalink
feat: use section locks (#560)
Browse files Browse the repository at this point in the history
  • Loading branch information
smartcmd authored Jan 30, 2025
1 parent 14ede80 commit 1288d9e
Show file tree
Hide file tree
Showing 24 changed files with 869 additions and 866 deletions.
3 changes: 3 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,23 @@ Unless otherwise specified, any version comparison below is the comparison of se
- (API) Added `ChunkService#removeUnusedChunksImmediately` method that can remove unused chunks immediately. Also, the `/gc` command
will call this method in all dimensions now.
- (API) Added `ItemBaseComponent#getLockMode` and `ItemBaseComponent#setLockMode` methods to get and set the lock mode of an item.
- (API) Added `ChunkSection`, chunk section can be obtained from chunk.
- Implemented reeds (also called sugar cane) and cactus.
- Implemented `UpdateSubChunkBlocksPacket` related logic, which will make client load large range block updates much quicker (e.g.
using `/fill` command to fill a large area).
- Introduced `ChunkSectionLocks`, which replaced the old `StampedLock` in `Chunk`. Instead of locking the whole chunk when reading/writing
blocks/biomes, only the related chunk section will be locked now. This should improve the performance of chunk reading/writing.

### Changed

- (API) Renamed `FullContainerTypeBuilder` to `Builder`.
- (API) Moved method `Chunk#isLoaded` to `UnsafeChunk#isLoaded`.
- (API) Made method `Dimension#createUpdateBlockPacket` private, consider using `Dimension#sendBlockUpdateTo` method instead.
- World will be skipped if failed to be load.
- (API) Moved and renamed `UnsafeChunk#index` method to `HashUtils#hashChunkSectionXYZ`.
- (API) Refactored `Chunk` and `UnsafeChunk`, now `Chunk` works more likely a wrapper for `UnsafeChunk` that provides
safe access to chunk data in multi-threads environment.
- (API) Replaced `Chunk#batchProcess` method with new `Chunk#applyOperation` and `Chunk#applyOperationInSection` methods.
- Main thread will sleep a short time if gui is enabled when the server exits abnormally. This gives user time to see what goes wrong.
- Server won't crash if failed to load the descriptor of a plugin now. An error message will be print to the console instead.
- Server won't crash if failed to create world generator. Void world generator will be used instead.
Expand All @@ -56,6 +63,7 @@ Unless otherwise specified, any version comparison below is the comparison of se
- Explosion damage now scales with game difficulty.
- Fixed a rare NPE exception that may occur if player disconnect when joining the server.
- Fixed missing block breaking particle when breaking block.
- Item entity will be despawned immediately instead of having a `dead timer` when its health become 0. It's dead smoke is also removed.

### Removed

Expand Down
55 changes: 41 additions & 14 deletions api/src/main/java/org/allaymc/api/utils/HashUtils.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
package org.allaymc.api.utils;

import com.google.common.base.Preconditions;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.allaymc.api.block.data.BlockId;
import org.allaymc.api.block.property.type.BlockPropertyType;
import org.allaymc.api.world.chunk.ChunkSection;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtUtils;
import org.jetbrains.annotations.Range;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.TreeMap;

/**
* Hash utilities.
* A utility class that contains hash related methods.
*
* @author Cool_Loong | daoge_cmd
*/
@Slf4j
@UtilityClass
public class HashUtils {
public final class HashUtils {

//https://gist.github.com/Alemiz112/504d0f79feac7ef57eda174b668dd345
private static final int FNV1_32_INIT = 0x811c9dc5;
private static final int FNV1_PRIME_32 = 0x01000193;

private HashUtils() {throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");}

/**
* Compute block state hash from the given identifier and property values.
*
Expand All @@ -33,7 +36,7 @@ public class HashUtils {
*
* @return the hash.
*/
public int computeBlockStateHash(Identifier identifier, List<BlockPropertyType.BlockPropertyValue<?, ?, ?>> propertyValues) {
public static int computeBlockStateHash(Identifier identifier, List<BlockPropertyType.BlockPropertyValue<?, ?, ?>> propertyValues) {
if (identifier.equals(BlockId.UNKNOWN.getIdentifier())) {
return -2; // This is special case
}
Expand All @@ -59,7 +62,7 @@ public int computeBlockStateHash(Identifier identifier, List<BlockPropertyType.B
*
* @return the hash.
*/
public int computeBlockStateHash(Identifier identifier, BlockPropertyType.BlockPropertyValue<?, ?, ?>[] propertyValues) {
public static int computeBlockStateHash(Identifier identifier, BlockPropertyType.BlockPropertyValue<?, ?, ?>[] propertyValues) {
if (identifier.equals(BlockId.UNKNOWN.getIdentifier())) {
return -2; // This is special case
}
Expand All @@ -84,7 +87,7 @@ public int computeBlockStateHash(Identifier identifier, BlockPropertyType.BlockP
*
* @return the hash.
*/
public int fnv1a_32_nbt(NbtMap tag) {
public static int fnv1a_32_nbt(NbtMap tag) {
byte[] bytes;
try (var stream = new ByteArrayOutputStream();
var outputStream = NbtUtils.createWriterLE(stream)) {
Expand All @@ -105,7 +108,7 @@ public int fnv1a_32_nbt(NbtMap tag) {
*
* @return the hash.
*/
public int fnv1a_32(byte[] data) {
public static int fnv1a_32(byte[] data) {
int hash = FNV1_32_INIT;
for (byte datum : data) {
hash ^= (datum & 0xff);
Expand All @@ -122,7 +125,7 @@ public int fnv1a_32(byte[] data) {
*
* @return the long.
*/
public long hashXZ(int x, int z) {
public static long hashXZ(int x, int z) {
return ((long) x << 32) | (z & 0xffffffffL);
}

Expand All @@ -131,7 +134,7 @@ public long hashXZ(int x, int z) {
*
* @param hashXZ a long value.
*/
public int getXFromHashXZ(long hashXZ) {
public static int getXFromHashXZ(long hashXZ) {
return (int) (hashXZ >> 32);
}

Expand All @@ -140,11 +143,19 @@ public int getXFromHashXZ(long hashXZ) {
*
* @param hashXZ a long value.
*/
public int getZFromHashXZ(long hashXZ) {
public static int getZFromHashXZ(long hashXZ) {
return (int) hashXZ;
}

public int hashChunkXYZ(int x, int y, int z) {
/**
* Calculate the hash of a pos in a chunk.
*
* @param x the x coordinate of the pos.
* @param z the z coordinate of the pos.
*
* @return the hash of a pos in a chunk.
*/
public static int hashChunkXYZ(@Range(from = 0, to = 15) int x, @Range(from = -8388608, to = 8388607) int y, @Range(from = 0, to = 15) int z) {
Preconditions.checkArgument(x >= 0 && x <= 15);
Preconditions.checkArgument(y >= -8388608 && y <= 8388607);
Preconditions.checkArgument(z >= 0 && z <= 15);
Expand All @@ -160,7 +171,7 @@ public int hashChunkXYZ(int x, int y, int z) {
*
* @return The value of x.
*/
public int getXFromHashChunkXYZ(int encoded) {
public static int getXFromHashChunkXYZ(int encoded) {
return (encoded >>> 28);
}

Expand All @@ -172,7 +183,7 @@ public int getXFromHashChunkXYZ(int encoded) {
*
* @return The value of y.
*/
public int getYFromHashChunkXYZ(int encoded) {
public static int getYFromHashChunkXYZ(int encoded) {
return ((encoded >>> 4) & 0xFFFFFF) - 8388608;
}

Expand All @@ -187,4 +198,20 @@ public int getYFromHashChunkXYZ(int encoded) {
public static int getZFromHashChunkXYZ(int encoded) {
return encoded & 0xF;
}

/**
* Calculate the hash of a pos in a {@link ChunkSection}.
*
* @param x the x coordinate of the pos.
* @param y the y coordinate of the pos.
* @param z the z coordinate of the pos.
*
* @return the hash of a pos in a {@link ChunkSection}.
*/
public static int hashChunkSectionXYZ(@Range(from = 0, to = 15) int x, @Range(from = 0, to = 15) int y, @Range(from = 0, to = 15) int z) {
Preconditions.checkArgument(x >= 0 && x <= 15);
Preconditions.checkArgument(y >= 0 && y <= 15);
Preconditions.checkArgument(z >= 0 && z <= 15);
return (x << 8) + (z << 4) + y;
}
}
64 changes: 40 additions & 24 deletions api/src/main/java/org/allaymc/api/world/Dimension.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.allaymc.api.utils.Utils;
import org.allaymc.api.world.biome.BiomeId;
import org.allaymc.api.world.biome.BiomeType;
import org.allaymc.api.world.chunk.OperationType;
import org.allaymc.api.world.service.*;
import org.apache.commons.lang3.function.TriFunction;
import org.cloudburstmc.protocol.bedrock.data.LevelEventType;
Expand Down Expand Up @@ -484,42 +485,39 @@ default void forEachBlockStates(int x, int y, int z, int sizeX, int sizeY, int s

var startX = x >> 4;
var endX = (x + sizeX - 1) >> 4;
var startY = y >> 4;
var endY = (y + sizeY - 1) >> 4;
var startZ = z >> 4;
var endZ = (z + sizeZ - 1) >> 4;
for (int chunkX = startX; chunkX <= endX; chunkX++) {
var cX = chunkX << 4;
var localStartX = Math.max(x - cX, 0);
var localEndX = Math.min(x + sizeX - cX, 16);

for (int chunkZ = startZ; chunkZ <= endZ; chunkZ++) {
var cZ = chunkZ << 4;
var localStartZ = Math.max(z - cZ, 0);
var localEndZ = Math.min(z + sizeZ - cZ, 16);

var chunk = getChunkService().getChunk(chunkX, chunkZ);
if (chunk != null) {
chunk.batchProcess(c -> {
var chunk = getChunkService().getOrLoadChunkSync(chunkX, chunkZ);
for (int sectionY = startY; sectionY <= endY; sectionY++) {
var cY = sectionY << 4;
var localStartY = Math.max(y - cY, 0);
var localEndY = Math.min(y + sizeY - cY, 16);

chunk.applyOperationInSection(sectionY, section -> {
for (int localX = localStartX; localX < localEndX; localX++) {
for (int globalY = y; globalY < y + sizeY; globalY++) {
for (int localY = localStartY; localY < localEndY; localY++) {
for (int localZ = localStartZ; localZ < localEndZ; localZ++) {
var globalX = cX + localX;
var globalY = cY + localY;
var globalZ = cZ + localZ;
var blockState = c.getBlockState(localX, globalY, localZ, layer);
var blockState = section.getBlockState(localX, localY, localZ, layer);
blockStateConsumer.apply(globalX, globalY, globalZ, blockState);
}
}
}
});
} else {
var air = AIR.getDefaultState();
for (int localX = localStartX; localX < localEndX; localX++) {
for (int globalY = y; globalY < y + sizeY; globalY++) {
for (int localZ = localStartZ; localZ < localEndZ; localZ++) {
var globalX = cX + localX;
var globalZ = cZ + localZ;
blockStateConsumer.apply(globalX, globalY, globalZ, air);
}
}
}
}, OperationType.READ, OperationType.NONE);
}
}
}
Expand All @@ -535,7 +533,8 @@ default void forEachBlockStates(int x, int y, int z, int sizeX, int sizeY, int s
* @param sizeY the size of the region in the y-axis.
* @param sizeZ the size of the region in the z-axis.
* @param layer the layer which the block will be set
* @param blockStateSupplier the block state supplier. The supplier will be called with the global x, y, z coordinates of the pos, and it should return the block state to set.
* @param blockStateSupplier the block state supplier. The supplier will be called with the global x, y, z coordinates of the pos,
* and it should return the block state to set. If the supplier returns {@code null}, the block state will keep unchanged.
*/
default void setBlockStates(int x, int y, int z, int sizeX, int sizeY, int sizeZ, int layer, TriFunction<Integer, Integer, Integer, BlockState> blockStateSupplier) {
if (sizeX < 1 || sizeY < 1 || sizeZ < 1) {
Expand All @@ -544,39 +543,56 @@ default void setBlockStates(int x, int y, int z, int sizeX, int sizeY, int sizeZ

var startX = x >> 4;
var endX = (x + sizeX - 1) >> 4;
var startY = y >> 4;
var endY = (y + sizeY - 1) >> 4;
var startZ = z >> 4;
var endZ = (z + sizeZ - 1) >> 4;
for (int chunkX = startX; chunkX <= endX; chunkX++) {
var cX = chunkX << 4;
var localStartX = Math.max(x - cX, 0);
var localEndX = Math.min(x + sizeX - cX, 16);

for (int chunkZ = startZ; chunkZ <= endZ; chunkZ++) {
var cZ = chunkZ << 4;
var localStartZ = Math.max(z - cZ, 0);
var localEndZ = Math.min(z + sizeZ - cZ, 16);

var chunk = getChunkService().getChunk(chunkX, chunkZ);
if (chunk != null) {
chunk.batchProcess(c -> {
var chunk = getChunkService().getOrLoadChunkSync(chunkX, chunkZ);
for (int sectionY = startY; sectionY <= endY; sectionY++) {
var cY = sectionY << 4;
var localStartY = Math.max(y - cY, 0);
var localEndY = Math.min(y + sizeY - cY, 16);

chunk.applyOperationInSection(sectionY, section -> {
for (int localX = localStartX; localX < localEndX; localX++) {
for (int globalY = y; globalY < y + sizeY; globalY++) {
for (int localY = localStartY; localY < localEndY; localY++) {
for (int localZ = localStartZ; localZ < localEndZ; localZ++) {
var globalX = cX + localX;
var globalY = cY + localY;
var globalZ = cZ + localZ;
c.setBlockState(localX, globalY, localZ, blockStateSupplier.apply(globalX, globalY, globalZ), layer);
var blockState = blockStateSupplier.apply(globalX, globalY, globalZ);
if (blockState != null) {
section.setBlockState(localX, localY, localZ, blockState, layer);
}
}
}
}
});
}, OperationType.WRITE, OperationType.NONE);
}
}
}
}

/**
* @see #updateBlockProperty(BlockPropertyType, Object, int, int, int, int)
*/
default <DATATYPE> void updateBlockProperty(BlockPropertyType<DATATYPE> propertyType, DATATYPE value, int x, int y, int z) {
updateBlockProperty(propertyType, value, x, y, z, 0);
}

/**
* @see #updateBlockProperty(BlockPropertyType, Object, int, int, int, int)
*/
default <DATATYPE> void updateBlockProperty(BlockPropertyType<DATATYPE> propertyType, DATATYPE value, Vector3ic pos) {
updateBlockProperty(propertyType, value, pos.x(), pos.y(), pos.z(), 0);
}
Expand Down
Loading

0 comments on commit 1288d9e

Please sign in to comment.