Skip to content
Open
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
89 changes: 85 additions & 4 deletions src/main/java/xyz/nucleoid/map_templates/MapChunk.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package xyz.nucleoid.map_templates;

import com.mojang.serialization.Codec;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
Expand All @@ -9,15 +14,29 @@
import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.chunk.PalettedContainer;
import org.apache.commons.codec.digest.Blake3;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public final class MapChunk {
private static final Codec<PalettedContainer<BlockState>> BLOCK_CODEC = PalettedContainer.createPalettedContainerCodec(Block.STATE_IDS, BlockState.CODEC, PalettedContainer.PaletteProvider.BLOCK_STATE, Blocks.AIR.getDefaultState());

private final ChunkSectionPos pos;

/**
* BLAKE3 hash of the block states in this chunk section
*/
private final byte[] blockStatesHash = new byte[32];

/**
* Whether the above hash represents currently-stored block data.
*/
private boolean blockStatesHashDirty = true;


private PalettedContainer<BlockState> container = new PalettedContainer<>(Block.STATE_IDS, Blocks.AIR.getDefaultState(), PalettedContainer.PaletteProvider.BLOCK_STATE);
private final List<MapEntity> entities = new ArrayList<>();

Expand All @@ -26,6 +45,7 @@ public final class MapChunk {
}

public void set(int x, int y, int z, BlockState state) {
this.blockStatesHashDirty = true;
this.container.set(x, y, z, state);
}

Expand Down Expand Up @@ -66,7 +86,8 @@ public List<MapEntity> getEntities() {
}

public void serialize(NbtCompound nbt) {
nbt.put("block_states", BLOCK_CODEC.encodeStart(NbtOps.INSTANCE, this.container).getOrThrow(false, (e) -> {}));
var blockStatesNbt = BLOCK_CODEC.encodeStart(NbtOps.INSTANCE, this.container).getOrThrow(false, (e) -> {});
nbt.put("block_states", blockStatesNbt);

if (!this.entities.isEmpty()) {
var entitiesNbt = new NbtList();
Expand All @@ -79,12 +100,11 @@ public void serialize(NbtCompound nbt) {

public static MapChunk deserialize(ChunkSectionPos pos, NbtCompound nbt) {
var chunk = new MapChunk(pos);
var blockStatesNbt = nbt.getCompound("block_states");
var container = BLOCK_CODEC.parse(NbtOps.INSTANCE, nbt.getCompound("block_states"))
.promotePartial(errorMessage -> {}).get().left();

if (container.isPresent()) {
chunk.container = container.get();
}
container.ifPresent(blockStatePalettedContainer -> chunk.container = blockStatePalettedContainer);

if (nbt.contains("entities", NbtElement.LIST_TYPE)) {
var entitiesNbt = nbt.getList("entities", NbtElement.COMPOUND_TYPE);
Expand All @@ -95,4 +115,65 @@ public static MapChunk deserialize(ChunkSectionPos pos, NbtCompound nbt) {

return chunk;
}

private NbtElement serializeBlockStates() {
return BLOCK_CODEC.encodeStart(NbtOps.INSTANCE, this.container).getOrThrow(false, (e) -> {});
}

/**
* Recompute the hash if it is dirty.
*/
private void recomputeHash() {
if (this.blockStatesHashDirty) {
try {
var md = Blake3.initHash();

this.serializeBlockStates().write(new DataOutputStream(new OutputStream() {
private ByteBuffer tmpBuffer;
@Override
public void write(int i) {
if (tmpBuffer == null) {
tmpBuffer = ByteBuffer.allocate(4);
} else {
tmpBuffer.clear();
}

tmpBuffer.putInt(i);
md.update(tmpBuffer.array());
}

@Override
public void write(byte @NotNull [] bytes, int off, int len) {
md.update(bytes, off, len);
}
}));

md.doFinalize(this.blockStatesHash, 0, 32);
} catch (IOException e) {
// Unreachable
throw new RuntimeException(e);
}

this.blockStatesHashDirty = false;
}
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (!(obj instanceof MapChunk)) {
return false;
}

MapChunk other = (MapChunk) obj;

if (!this.pos.equals(other.pos)) {
return false;
}

this.recomputeHash();
other.recomputeHash();
return Arrays.equals(this.blockStatesHash, other.blockStatesHash);
}
}
48 changes: 41 additions & 7 deletions src/main/java/xyz/nucleoid/map_templates/MapTemplate.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package xyz.nucleoid.map_templates;

import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.*;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntity;
Expand All @@ -13,10 +11,7 @@
import net.minecraft.util.BlockMirror;
import net.minecraft.util.BlockRotation;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.*;
import net.minecraft.world.Heightmap;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeKeys;
Expand Down Expand Up @@ -366,6 +361,45 @@ public static MapTemplate merged(MapTemplate primary, MapTemplate secondary) {
return result;
}

/**
* All the aspects in which two map templates differ.
*
* @param dataChanged whether the metadata has changed
* @param chunksChanged whether the chunks and blocks of the template has changed
* @param biomeChanged whether the biome of the template has changed
* @param blockEntitiesChanged whether the block entities in the template have changed
*/
public record Diff(boolean dataChanged, boolean chunksChanged, boolean biomeChanged, boolean blockEntitiesChanged) {
public boolean needsRegeneration() {
return this.chunksChanged || this.biomeChanged || this.blockEntitiesChanged;
}
}

/**
* Calculate the difference in chunks between the two semplates.
*
* @param a the first template
* @param b the second template
*/
public static Diff diff(MapTemplate a, MapTemplate b) {
if (a == b) {
return new Diff(false, false, false, false);
}

boolean chunksChanged = !(a.chunks.equals(b.chunks));
boolean dataChanged = !a.getMetadata().equals(b.getMetadata());
boolean biomeChanged = a.biome != b.biome;
boolean blockEntitiesChanged = !a.blockEntities.equals(b.blockEntities);
return new Diff(dataChanged, chunksChanged, biomeChanged, blockEntitiesChanged);
}

/**
* Calculate the difference in chunks between this and another template.
*/
public Diff diff(MapTemplate other) {
return MapTemplate.diff(this, other);
}

public void mergeFrom(MapTemplate other) {
other.mergeInto(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,12 @@ public void setData(NbtCompound data) {
public NbtCompound getData() {
return this.data;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MapTemplateMetadata)) return false;

return this.data.equals(((MapTemplateMetadata) o).data) && this.regions.equals(((MapTemplateMetadata) o).regions);
}
}
7 changes: 7 additions & 0 deletions src/main/java/xyz/nucleoid/map_templates/TemplateRegion.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,11 @@ public static TemplateRegion deserialize(NbtCompound nbt) {
public TemplateRegion copy() {
return new TemplateRegion(this.marker, this.bounds, this.data != null ? this.data.copy() : null);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TemplateRegion other)) return false;
return this.data.equals(other.data) && this.marker.equals(other.marker) && this.bounds.equals(other.bounds);
}
}