2020-08-15 16:01:47 +02:00
|
|
|
package net.minestom.server.instance;
|
|
|
|
|
|
|
|
import com.extollit.gaming.ai.path.model.ColumnarOcclusionFieldList;
|
2021-05-23 00:28:31 +02:00
|
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
2021-11-12 18:35:09 +01:00
|
|
|
import net.minestom.server.MinecraftServer;
|
2021-09-12 02:34:29 +02:00
|
|
|
import net.minestom.server.coordinate.Point;
|
2023-07-02 21:34:54 +02:00
|
|
|
import net.minestom.server.coordinate.Vec;
|
2022-03-03 07:44:57 +01:00
|
|
|
import net.minestom.server.entity.Entity;
|
2021-06-23 22:24:40 +02:00
|
|
|
import net.minestom.server.entity.pathfinding.PFBlock;
|
2021-05-23 00:28:31 +02:00
|
|
|
import net.minestom.server.instance.block.Block;
|
2021-05-29 00:07:22 +02:00
|
|
|
import net.minestom.server.instance.block.BlockHandler;
|
2022-10-29 11:02:22 +02:00
|
|
|
import net.minestom.server.network.NetworkBuffer;
|
2021-11-17 00:48:43 +01:00
|
|
|
import net.minestom.server.network.packet.server.CachedPacket;
|
2024-01-06 08:43:43 +01:00
|
|
|
import net.minestom.server.network.packet.server.SendablePacket;
|
2020-08-15 16:01:47 +02:00
|
|
|
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
2021-08-04 16:49:01 +02:00
|
|
|
import net.minestom.server.network.packet.server.play.UpdateLightPacket;
|
2021-10-21 10:36:14 +02:00
|
|
|
import net.minestom.server.network.packet.server.play.data.ChunkData;
|
|
|
|
import net.minestom.server.network.packet.server.play.data.LightData;
|
2022-03-03 07:44:57 +01:00
|
|
|
import net.minestom.server.snapshot.ChunkSnapshot;
|
2022-05-04 13:25:24 +02:00
|
|
|
import net.minestom.server.snapshot.SnapshotImpl;
|
2022-03-03 07:44:57 +01:00
|
|
|
import net.minestom.server.snapshot.SnapshotUpdater;
|
|
|
|
import net.minestom.server.utils.ArrayUtils;
|
2021-10-05 23:29:05 +02:00
|
|
|
import net.minestom.server.utils.MathUtils;
|
2022-05-16 07:34:47 +02:00
|
|
|
import net.minestom.server.utils.ObjectPool;
|
2020-09-23 21:08:36 +02:00
|
|
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
2021-11-12 18:35:09 +01:00
|
|
|
import net.minestom.server.world.biomes.Biome;
|
2024-02-12 21:25:46 +01:00
|
|
|
import net.minestom.server.world.biomes.BiomeManager;
|
2020-10-24 10:46:23 +02:00
|
|
|
import org.jetbrains.annotations.NotNull;
|
2020-10-29 22:52:07 +01:00
|
|
|
import org.jetbrains.annotations.Nullable;
|
2021-12-13 16:41:30 +01:00
|
|
|
import org.jglrxavpok.hephaistos.nbt.NBT;
|
2021-10-05 23:29:05 +02:00
|
|
|
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
2023-08-05 19:10:37 +02:00
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
2020-08-15 16:01:47 +02:00
|
|
|
|
2021-10-20 21:31:59 +02:00
|
|
|
import java.util.*;
|
2020-08-15 16:01:47 +02:00
|
|
|
|
2021-12-22 22:16:55 +01:00
|
|
|
import static net.minestom.server.utils.chunk.ChunkUtils.toSectionRelativeCoordinate;
|
|
|
|
|
2020-10-11 18:35:32 +02:00
|
|
|
/**
|
|
|
|
* Represents a {@link Chunk} which store each individual block in memory.
|
2020-11-22 16:32:15 +01:00
|
|
|
* <p>
|
2020-11-25 09:47:04 +01:00
|
|
|
* WARNING: not thread-safe.
|
2020-10-11 18:35:32 +02:00
|
|
|
*/
|
2020-08-15 16:01:47 +02:00
|
|
|
public class DynamicChunk extends Chunk {
|
2023-08-05 19:10:37 +02:00
|
|
|
private static final Logger LOGGER = LoggerFactory.getLogger(DynamicChunk.class);
|
2020-08-15 16:01:47 +02:00
|
|
|
|
2023-05-28 01:41:14 +02:00
|
|
|
protected List<Section> sections;
|
2020-08-16 00:53:42 +02:00
|
|
|
|
2021-05-29 00:07:22 +02:00
|
|
|
// Key = ChunkUtils#getBlockIndex
|
2021-11-06 10:32:57 +01:00
|
|
|
protected final Int2ObjectOpenHashMap<Block> entries = new Int2ObjectOpenHashMap<>(0);
|
|
|
|
protected final Int2ObjectOpenHashMap<Block> tickableMap = new Int2ObjectOpenHashMap<>(0);
|
2020-10-05 10:03:25 +02:00
|
|
|
|
2021-10-19 12:41:34 +02:00
|
|
|
private long lastChange;
|
2022-06-30 23:12:42 +02:00
|
|
|
final CachedPacket chunkCache = new CachedPacket(this::createChunkPacket);
|
2024-02-12 21:25:46 +01:00
|
|
|
private static final BiomeManager BIOME_MANAGER = MinecraftServer.getBiomeManager();
|
2021-03-28 17:13:18 +02:00
|
|
|
|
2021-11-06 06:24:45 +01:00
|
|
|
public DynamicChunk(@NotNull Instance instance, int chunkX, int chunkZ) {
|
|
|
|
super(instance, chunkX, chunkZ, true);
|
2021-12-22 01:47:28 +01:00
|
|
|
var sectionsTemp = new Section[maxSection - minSection];
|
|
|
|
Arrays.setAll(sectionsTemp, value -> new Section());
|
|
|
|
this.sections = List.of(sectionsTemp);
|
2020-08-16 00:53:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2023-07-02 21:34:54 +02:00
|
|
|
public void setBlock(int x, int y, int z, @NotNull Block block,
|
|
|
|
@Nullable BlockHandler.Placement placement,
|
|
|
|
@Nullable BlockHandler.Destroy destroy) {
|
2023-08-05 19:10:37 +02:00
|
|
|
if(y >= instance.getDimensionType().getMaxY() || y < instance.getDimensionType().getMinY()) {
|
|
|
|
LOGGER.warn("tried to set a block outside the world bounds, should be within [{}, {}): {}",
|
|
|
|
instance.getDimensionType().getMinY(), instance.getDimensionType().getMaxY(), y);
|
|
|
|
return;
|
|
|
|
}
|
2022-03-17 00:14:12 +01:00
|
|
|
assertLock();
|
2023-08-05 19:10:37 +02:00
|
|
|
|
2021-10-19 12:41:34 +02:00
|
|
|
this.lastChange = System.currentTimeMillis();
|
|
|
|
this.chunkCache.invalidate();
|
2023-05-28 01:41:14 +02:00
|
|
|
|
2021-06-18 14:36:03 +02:00
|
|
|
// Update pathfinder
|
|
|
|
if (columnarSpace != null) {
|
|
|
|
final ColumnarOcclusionFieldList columnarOcclusionFieldList = columnarSpace.occlusionFields();
|
2021-06-23 22:24:40 +02:00
|
|
|
final var blockDescription = PFBlock.get(block);
|
2021-06-18 14:36:03 +02:00
|
|
|
columnarOcclusionFieldList.onBlockChanged(x, y, z, blockDescription, 0);
|
2021-06-12 10:39:44 +02:00
|
|
|
}
|
2021-11-12 18:35:09 +01:00
|
|
|
Section section = getSectionAt(y);
|
2023-07-02 21:34:54 +02:00
|
|
|
section.blockPalette().set(
|
|
|
|
toSectionRelativeCoordinate(x),
|
|
|
|
toSectionRelativeCoordinate(y),
|
|
|
|
toSectionRelativeCoordinate(z),
|
|
|
|
block.stateId()
|
|
|
|
);
|
2021-06-12 10:39:44 +02:00
|
|
|
|
2021-06-22 14:13:51 +02:00
|
|
|
final int index = ChunkUtils.getBlockIndex(x, y, z);
|
2021-05-29 00:07:22 +02:00
|
|
|
// Handler
|
2021-06-19 21:01:54 +02:00
|
|
|
final BlockHandler handler = block.handler();
|
2023-07-02 21:34:54 +02:00
|
|
|
final Block lastCachedBlock;
|
2021-08-21 04:53:43 +02:00
|
|
|
if (handler != null || block.hasNbt() || block.registry().isBlockEntity()) {
|
2023-07-02 21:34:54 +02:00
|
|
|
lastCachedBlock = this.entries.put(index, block);
|
2020-08-16 00:53:42 +02:00
|
|
|
} else {
|
2023-07-02 21:34:54 +02:00
|
|
|
lastCachedBlock = this.entries.remove(index);
|
2021-07-10 20:30:35 +02:00
|
|
|
}
|
|
|
|
// Block tick
|
|
|
|
if (handler != null && handler.isTickable()) {
|
|
|
|
this.tickableMap.put(index, block);
|
|
|
|
} else {
|
2021-06-18 14:36:03 +02:00
|
|
|
this.tickableMap.remove(index);
|
|
|
|
}
|
2023-07-02 21:34:54 +02:00
|
|
|
|
|
|
|
// Update block handlers
|
|
|
|
var blockPosition = new Vec(x, y, z);
|
|
|
|
if (lastCachedBlock != null && lastCachedBlock.handler() != null) {
|
|
|
|
// Previous destroy
|
|
|
|
lastCachedBlock.handler().onDestroy(Objects.requireNonNullElseGet(destroy,
|
|
|
|
() -> new BlockHandler.Destroy(lastCachedBlock, instance, blockPosition)));
|
|
|
|
}
|
|
|
|
if (handler != null) {
|
|
|
|
// New placement
|
|
|
|
final Block finalBlock = block;
|
|
|
|
handler.onPlace(Objects.requireNonNullElseGet(placement,
|
|
|
|
() -> new BlockHandler.Placement(finalBlock, instance, blockPosition)));
|
|
|
|
}
|
|
|
|
|
2020-08-16 00:53:42 +02:00
|
|
|
}
|
|
|
|
|
2021-11-12 18:35:09 +01:00
|
|
|
@Override
|
|
|
|
public void setBiome(int x, int y, int z, @NotNull Biome biome) {
|
2022-03-17 00:14:12 +01:00
|
|
|
assertLock();
|
2021-11-12 18:35:09 +01:00
|
|
|
this.chunkCache.invalidate();
|
|
|
|
Section section = getSectionAt(y);
|
2024-02-12 21:25:46 +01:00
|
|
|
|
|
|
|
var id = BIOME_MANAGER.getId(biome);
|
|
|
|
if (id == -1) throw new IllegalStateException("Biome has not been registered: " + biome.namespace());
|
|
|
|
|
2021-12-20 01:07:27 +01:00
|
|
|
section.biomePalette().set(
|
2021-12-22 22:16:55 +01:00
|
|
|
toSectionRelativeCoordinate(x) / 4,
|
|
|
|
toSectionRelativeCoordinate(y) / 4,
|
2024-02-12 21:25:46 +01:00
|
|
|
toSectionRelativeCoordinate(z) / 4, id);
|
2021-11-12 18:35:09 +01:00
|
|
|
}
|
|
|
|
|
2021-12-22 01:47:28 +01:00
|
|
|
@Override
|
|
|
|
public @NotNull List<Section> getSections() {
|
|
|
|
return sections;
|
|
|
|
}
|
|
|
|
|
2021-06-12 10:39:44 +02:00
|
|
|
@Override
|
2021-06-21 16:32:46 +02:00
|
|
|
public @NotNull Section getSection(int section) {
|
2021-12-22 01:47:28 +01:00
|
|
|
return sections.get(section - minSection);
|
2021-06-12 10:39:44 +02:00
|
|
|
}
|
|
|
|
|
2020-10-05 10:03:25 +02:00
|
|
|
@Override
|
2021-03-11 20:54:30 +01:00
|
|
|
public void tick(long time) {
|
2021-08-16 20:08:48 +02:00
|
|
|
if (tickableMap.isEmpty()) return;
|
2021-11-09 12:56:32 +01:00
|
|
|
tickableMap.int2ObjectEntrySet().fastForEach(entry -> {
|
2021-06-18 14:43:14 +02:00
|
|
|
final int index = entry.getIntKey();
|
2021-07-10 18:42:02 +02:00
|
|
|
final Block block = entry.getValue();
|
2021-08-16 20:08:48 +02:00
|
|
|
final BlockHandler handler = block.handler();
|
|
|
|
if (handler == null) return;
|
2021-09-12 02:34:29 +02:00
|
|
|
final Point blockPosition = ChunkUtils.getBlockPosition(index, chunkX, chunkZ);
|
2021-08-16 20:08:48 +02:00
|
|
|
handler.tick(new BlockHandler.Tick(block, instance, blockPosition));
|
|
|
|
});
|
2020-08-16 00:53:42 +02:00
|
|
|
}
|
|
|
|
|
2020-10-05 10:03:25 +02:00
|
|
|
@Override
|
2021-07-11 20:44:37 +02:00
|
|
|
public @Nullable Block getBlock(int x, int y, int z, @NotNull Condition condition) {
|
2022-03-17 00:14:12 +01:00
|
|
|
assertLock();
|
2022-01-05 23:02:12 +01:00
|
|
|
if (y < minSection * CHUNK_SECTION_SIZE || y >= maxSection * CHUNK_SECTION_SIZE)
|
|
|
|
return Block.AIR; // Out of bounds
|
|
|
|
|
2021-07-10 18:42:02 +02:00
|
|
|
// Verify if the block object is present
|
2021-09-18 16:18:41 +02:00
|
|
|
if (condition != Condition.TYPE) {
|
|
|
|
final Block entry = !entries.isEmpty() ?
|
|
|
|
entries.get(ChunkUtils.getBlockIndex(x, y, z)) : null;
|
|
|
|
if (entry != null || condition == Condition.CACHED) {
|
|
|
|
return entry;
|
|
|
|
}
|
2021-07-10 18:42:02 +02:00
|
|
|
}
|
|
|
|
// Retrieve the block from state id
|
2021-12-22 01:47:28 +01:00
|
|
|
final Section section = getSectionAt(y);
|
2021-11-29 20:12:11 +01:00
|
|
|
final int blockStateId = section.blockPalette()
|
2021-12-22 22:16:55 +01:00
|
|
|
.get(toSectionRelativeCoordinate(x), toSectionRelativeCoordinate(y), toSectionRelativeCoordinate(z));
|
2021-11-05 15:58:35 +01:00
|
|
|
return Objects.requireNonNullElse(Block.fromStateId((short) blockStateId), Block.AIR);
|
2020-10-05 10:03:25 +02:00
|
|
|
}
|
|
|
|
|
2021-11-12 18:35:09 +01:00
|
|
|
@Override
|
|
|
|
public @NotNull Biome getBiome(int x, int y, int z) {
|
2022-03-17 00:14:12 +01:00
|
|
|
assertLock();
|
2021-12-22 01:47:28 +01:00
|
|
|
final Section section = getSectionAt(y);
|
2021-11-29 20:12:11 +01:00
|
|
|
final int id = section.biomePalette()
|
2022-01-07 22:48:07 +01:00
|
|
|
.get(toSectionRelativeCoordinate(x) / 4, toSectionRelativeCoordinate(y) / 4, toSectionRelativeCoordinate(z) / 4);
|
2024-02-12 21:25:46 +01:00
|
|
|
|
|
|
|
Biome biome = BIOME_MANAGER.getById(id);
|
|
|
|
if (biome == null) {
|
|
|
|
throw new IllegalStateException("Biome with id " + id + " is not registered");
|
|
|
|
}
|
|
|
|
|
|
|
|
return biome;
|
2021-11-12 18:35:09 +01:00
|
|
|
}
|
|
|
|
|
2020-11-20 08:39:06 +01:00
|
|
|
@Override
|
|
|
|
public long getLastChangeTime() {
|
2021-10-19 12:41:34 +02:00
|
|
|
return lastChange;
|
2020-11-20 08:39:06 +01:00
|
|
|
}
|
|
|
|
|
2020-08-16 00:53:42 +02:00
|
|
|
@Override
|
2024-01-06 08:43:43 +01:00
|
|
|
public @NotNull SendablePacket getFullDataPacket() {
|
|
|
|
return chunkCache;
|
2020-08-16 00:53:42 +02:00
|
|
|
}
|
2020-08-15 16:01:47 +02:00
|
|
|
|
2020-10-31 19:22:23 +01:00
|
|
|
@Override
|
2021-11-28 12:56:59 +01:00
|
|
|
public @NotNull Chunk copy(@NotNull Instance instance, int chunkX, int chunkZ) {
|
2021-11-06 06:24:45 +01:00
|
|
|
DynamicChunk dynamicChunk = new DynamicChunk(instance, chunkX, chunkZ);
|
2021-12-22 01:47:28 +01:00
|
|
|
dynamicChunk.sections = sections.stream().map(Section::clone).toList();
|
2021-07-09 00:42:43 +02:00
|
|
|
dynamicChunk.entries.putAll(entries);
|
2020-10-31 19:22:23 +01:00
|
|
|
return dynamicChunk;
|
|
|
|
}
|
2020-11-20 08:39:06 +01:00
|
|
|
|
2020-11-25 09:47:04 +01:00
|
|
|
@Override
|
|
|
|
public void reset() {
|
2021-12-03 21:37:15 +01:00
|
|
|
for (Section section : sections) section.clear();
|
2021-07-09 00:42:43 +02:00
|
|
|
this.entries.clear();
|
2020-11-25 09:47:04 +01:00
|
|
|
}
|
|
|
|
|
2024-02-11 00:02:02 +01:00
|
|
|
@Override
|
|
|
|
public void invalidate() {
|
|
|
|
this.chunkCache.invalidate();
|
|
|
|
}
|
|
|
|
|
2023-05-28 01:41:14 +02:00
|
|
|
private @NotNull ChunkDataPacket createChunkPacket() {
|
2024-02-04 20:16:15 +01:00
|
|
|
final NBTCompound heightmapsNBT = computeHeightmap();
|
2021-10-21 10:36:14 +02:00
|
|
|
// Data
|
2023-05-28 01:41:14 +02:00
|
|
|
|
|
|
|
final byte[] data;
|
|
|
|
synchronized (this) {
|
|
|
|
data = ObjectPool.PACKET_POOL.use(buffer ->
|
|
|
|
NetworkBuffer.makeArray(networkBuffer -> {
|
|
|
|
for (Section section : sections) networkBuffer.write(section);
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2021-10-23 13:47:56 +02:00
|
|
|
return new ChunkDataPacket(chunkX, chunkZ,
|
2022-05-16 07:34:47 +02:00
|
|
|
new ChunkData(heightmapsNBT, data, entries),
|
2024-02-04 20:16:15 +01:00
|
|
|
createLightData()
|
2023-06-18 21:16:09 +02:00
|
|
|
);
|
2021-08-04 16:49:01 +02:00
|
|
|
}
|
|
|
|
|
2024-02-04 20:16:15 +01:00
|
|
|
protected NBTCompound computeHeightmap() {
|
|
|
|
// TODO: don't hardcode heightmaps
|
|
|
|
// Heightmap
|
|
|
|
int dimensionHeight = getInstance().getDimensionType().getHeight();
|
|
|
|
int[] motionBlocking = new int[16 * 16];
|
|
|
|
int[] worldSurface = new int[16 * 16];
|
|
|
|
for (int x = 0; x < 16; x++) {
|
|
|
|
for (int z = 0; z < 16; z++) {
|
|
|
|
motionBlocking[x + z * 16] = 0;
|
|
|
|
worldSurface[x + z * 16] = dimensionHeight - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
final int bitsForHeight = MathUtils.bitsToRepresent(dimensionHeight);
|
|
|
|
return NBT.Compound(Map.of(
|
|
|
|
"MOTION_BLOCKING", NBT.LongArray(encodeBlocks(motionBlocking, bitsForHeight)),
|
|
|
|
"WORLD_SURFACE", NBT.LongArray(encodeBlocks(worldSurface, bitsForHeight))));
|
2021-10-20 21:31:59 +02:00
|
|
|
}
|
2021-08-04 16:49:01 +02:00
|
|
|
|
2024-02-04 20:16:15 +01:00
|
|
|
@NotNull UpdateLightPacket createLightPacket() {
|
|
|
|
return new UpdateLightPacket(chunkX, chunkZ, createLightData());
|
2023-06-18 21:16:09 +02:00
|
|
|
}
|
|
|
|
|
2024-02-04 20:16:15 +01:00
|
|
|
protected LightData createLightData() {
|
2021-10-20 21:31:59 +02:00
|
|
|
BitSet skyMask = new BitSet();
|
|
|
|
BitSet blockMask = new BitSet();
|
|
|
|
BitSet emptySkyMask = new BitSet();
|
|
|
|
BitSet emptyBlockMask = new BitSet();
|
|
|
|
List<byte[]> skyLights = new ArrayList<>();
|
|
|
|
List<byte[]> blockLights = new ArrayList<>();
|
2021-08-04 16:49:01 +02:00
|
|
|
|
2021-11-06 12:25:35 +01:00
|
|
|
int index = 0;
|
|
|
|
for (Section section : sections) {
|
|
|
|
index++;
|
2023-05-28 01:41:14 +02:00
|
|
|
final byte[] skyLight = section.skyLight().array();
|
|
|
|
final byte[] blockLight = section.blockLight().array();
|
2021-12-16 22:20:57 +01:00
|
|
|
if (skyLight.length != 0) {
|
2021-12-03 21:37:15 +01:00
|
|
|
skyLights.add(skyLight);
|
|
|
|
skyMask.set(index);
|
2021-12-16 22:35:34 +01:00
|
|
|
} else {
|
|
|
|
emptySkyMask.set(index);
|
2021-12-03 21:37:15 +01:00
|
|
|
}
|
2021-12-16 22:20:57 +01:00
|
|
|
if (blockLight.length != 0) {
|
2021-12-03 21:37:15 +01:00
|
|
|
blockLights.add(blockLight);
|
|
|
|
blockMask.set(index);
|
2021-12-16 22:35:34 +01:00
|
|
|
} else {
|
|
|
|
emptyBlockMask.set(index);
|
2021-08-04 16:49:01 +02:00
|
|
|
}
|
|
|
|
}
|
2023-06-15 04:11:18 +02:00
|
|
|
return new LightData(
|
2021-10-20 21:31:59 +02:00
|
|
|
skyMask, blockMask,
|
|
|
|
emptySkyMask, emptyBlockMask,
|
2023-06-15 04:11:18 +02:00
|
|
|
skyLights, blockLights
|
|
|
|
);
|
2021-08-04 16:49:01 +02:00
|
|
|
}
|
|
|
|
|
2022-03-03 07:44:57 +01:00
|
|
|
@Override
|
|
|
|
public @NotNull ChunkSnapshot updateSnapshot(@NotNull SnapshotUpdater updater) {
|
|
|
|
Section[] clonedSections = new Section[sections.size()];
|
|
|
|
for (int i = 0; i < clonedSections.length; i++)
|
|
|
|
clonedSections[i] = sections.get(i).clone();
|
|
|
|
var entities = instance.getEntityTracker().chunkEntities(chunkX, chunkZ, EntityTracker.Target.ENTITIES);
|
|
|
|
final int[] entityIds = ArrayUtils.mapToIntArray(entities, Entity::getEntityId);
|
2022-05-04 13:25:24 +02:00
|
|
|
return new SnapshotImpl.Chunk(minSection, chunkX, chunkZ,
|
2022-03-03 07:44:57 +01:00
|
|
|
clonedSections, entries.clone(), entityIds, updater.reference(instance),
|
2022-03-20 01:47:57 +01:00
|
|
|
tagHandler().readableCopy());
|
2022-03-03 07:44:57 +01:00
|
|
|
}
|
2022-03-17 00:14:12 +01:00
|
|
|
|
|
|
|
private void assertLock() {
|
|
|
|
assert Thread.holdsLock(this) : "Chunk must be locked before access";
|
|
|
|
}
|
2022-10-28 19:27:48 +02:00
|
|
|
|
|
|
|
private static final int[] MAGIC = {
|
|
|
|
-1, -1, 0, Integer.MIN_VALUE, 0, 0, 1431655765, 1431655765, 0, Integer.MIN_VALUE,
|
|
|
|
0, 1, 858993459, 858993459, 0, 715827882, 715827882, 0, 613566756, 613566756,
|
|
|
|
0, Integer.MIN_VALUE, 0, 2, 477218588, 477218588, 0, 429496729, 429496729, 0,
|
|
|
|
390451572, 390451572, 0, 357913941, 357913941, 0, 330382099, 330382099, 0, 306783378,
|
|
|
|
306783378, 0, 286331153, 286331153, 0, Integer.MIN_VALUE, 0, 3, 252645135, 252645135,
|
|
|
|
0, 238609294, 238609294, 0, 226050910, 226050910, 0, 214748364, 214748364, 0,
|
|
|
|
204522252, 204522252, 0, 195225786, 195225786, 0, 186737708, 186737708, 0, 178956970,
|
|
|
|
178956970, 0, 171798691, 171798691, 0, 165191049, 165191049, 0, 159072862, 159072862,
|
|
|
|
0, 153391689, 153391689, 0, 148102320, 148102320, 0, 143165576, 143165576, 0,
|
|
|
|
138547332, 138547332, 0, Integer.MIN_VALUE, 0, 4, 130150524, 130150524, 0, 126322567,
|
|
|
|
126322567, 0, 122713351, 122713351, 0, 119304647, 119304647, 0, 116080197, 116080197,
|
|
|
|
0, 113025455, 113025455, 0, 110127366, 110127366, 0, 107374182, 107374182, 0,
|
|
|
|
104755299, 104755299, 0, 102261126, 102261126, 0, 99882960, 99882960, 0, 97612893,
|
|
|
|
97612893, 0, 95443717, 95443717, 0, 93368854, 93368854, 0, 91382282, 91382282,
|
|
|
|
0, 89478485, 89478485, 0, 87652393, 87652393, 0, 85899345, 85899345, 0,
|
|
|
|
84215045, 84215045, 0, 82595524, 82595524, 0, 81037118, 81037118, 0, 79536431,
|
|
|
|
79536431, 0, 78090314, 78090314, 0, 76695844, 76695844, 0, 75350303, 75350303,
|
|
|
|
0, 74051160, 74051160, 0, 72796055, 72796055, 0, 71582788, 71582788, 0,
|
|
|
|
70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Integer.MIN_VALUE,
|
|
|
|
0, 5};
|
|
|
|
|
2024-02-04 20:16:15 +01:00
|
|
|
static long[] encodeBlocks(int[] blocks, int bitsPerEntry) {
|
2022-10-28 19:27:48 +02:00
|
|
|
final long maxEntryValue = (1L << bitsPerEntry) - 1;
|
|
|
|
final char valuesPerLong = (char) (64 / bitsPerEntry);
|
|
|
|
final int magicIndex = 3 * (valuesPerLong - 1);
|
|
|
|
final long divideMul = Integer.toUnsignedLong(MAGIC[magicIndex]);
|
|
|
|
final long divideAdd = Integer.toUnsignedLong(MAGIC[magicIndex + 1]);
|
|
|
|
final int divideShift = MAGIC[magicIndex + 2];
|
|
|
|
final int size = (blocks.length + valuesPerLong - 1) / valuesPerLong;
|
|
|
|
|
|
|
|
long[] data = new long[size];
|
|
|
|
|
|
|
|
for (int i = 0; i < blocks.length; i++) {
|
|
|
|
final long value = blocks[i];
|
|
|
|
final int cellIndex = (int) (i * divideMul + divideAdd >> 32L >> divideShift);
|
|
|
|
final int bitIndex = (i - cellIndex * valuesPerLong) * bitsPerEntry;
|
|
|
|
data[cellIndex] = data[cellIndex] & ~(maxEntryValue << bitIndex) | (value & maxEntryValue) << bitIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
2021-06-12 13:52:44 +02:00
|
|
|
}
|