mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +00:00
Columnar object mutation log
- Log transaction writes in parallel arrays instead of mutation records - Replay source plans straight from their packed cells without decoding records - Build source plans from a log slice - Journal commit originals in a packed-key set instead of a linked map - Buffer cave object writes in parallel arrays - Drop the now-unused import in the runner
This commit is contained in:
1 parent
fd47240db9
commit
ad73c33480
6 files changed
+410
-191
No files matched your search
+67
-65
@@ -30,11 +30,12 @@ import art.arcane.iris.generation.hydrology.cave.HydrologyCaveCell;
|
|||||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
import art.arcane.volmlib.util.math.BlockPosition;
|
import art.arcane.volmlib.util.math.BlockPosition;
|
||||||
import art.arcane.iris.generation.block.B;
|
import art.arcane.iris.generation.block.B;
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
final class CaveObjectPlacementTransaction implements IObjectPlacer {
|
final class CaveObjectPlacementTransaction implements IObjectPlacer {
|
||||||
private static final int CACHE_MISS = Integer.MIN_VALUE;
|
private static final int CACHE_MISS = Integer.MIN_VALUE;
|
||||||
private static final int POSITION_BITS = 21;
|
private static final int POSITION_BITS = 21;
|
||||||
@@ -46,7 +47,16 @@ final class CaveObjectPlacementTransaction implements IObjectPlacer {
|
|||||||
private final int anchorY;
|
private final int anchorY;
|
||||||
private final int minDepthBelowSurface;
|
private final int minDepthBelowSurface;
|
||||||
private final int worldHeight;
|
private final int worldHeight;
|
||||||
private final KList<BufferedMutation> mutations;
|
private static final byte BLOCK = 0;
|
||||||
|
private static final byte TILE = 1;
|
||||||
|
private static final byte DATA = 2;
|
||||||
|
private int[] mutationX = new int[64];
|
||||||
|
private int[] mutationY = new int[64];
|
||||||
|
private int[] mutationZ = new int[64];
|
||||||
|
private Object[] mutationValues = new Object[64];
|
||||||
|
private byte[] mutationKinds = new byte[64];
|
||||||
|
private boolean[] mutationCanopies = new boolean[64];
|
||||||
|
private int mutationCount;
|
||||||
private final Long2ObjectOpenHashMap<NativeBlockState> bufferedBlocks;
|
private final Long2ObjectOpenHashMap<NativeBlockState> bufferedBlocks;
|
||||||
private final Long2IntOpenHashMap surfaceHeights;
|
private final Long2IntOpenHashMap surfaceHeights;
|
||||||
private final Long2IntOpenHashMap caveCeilings;
|
private final Long2IntOpenHashMap caveCeilings;
|
||||||
@@ -62,7 +72,6 @@ final class CaveObjectPlacementTransaction implements IObjectPlacer {
|
|||||||
this.anchorY = anchorY;
|
this.anchorY = anchorY;
|
||||||
this.minDepthBelowSurface = Math.max(0, minDepthBelowSurface);
|
this.minDepthBelowSurface = Math.max(0, minDepthBelowSurface);
|
||||||
this.worldHeight = engine == null ? 0 : engine.getHeight();
|
this.worldHeight = engine == null ? 0 : engine.getHeight();
|
||||||
this.mutations = new KList<>();
|
|
||||||
this.bufferedBlocks = new Long2ObjectOpenHashMap<>();
|
this.bufferedBlocks = new Long2ObjectOpenHashMap<>();
|
||||||
this.surfaceHeights = new Long2IntOpenHashMap();
|
this.surfaceHeights = new Long2IntOpenHashMap();
|
||||||
this.caveCeilings = new Long2IntOpenHashMap();
|
this.caveCeilings = new Long2IntOpenHashMap();
|
||||||
@@ -81,35 +90,37 @@ final class CaveObjectPlacementTransaction implements IObjectPlacer {
|
|||||||
int previousX = 0;
|
int previousX = 0;
|
||||||
int previousY = 0;
|
int previousY = 0;
|
||||||
int previousZ = 0;
|
int previousZ = 0;
|
||||||
for (BufferedMutation mutation : mutations) {
|
for (int index = 0; index < mutationCount; index++) {
|
||||||
if (previousChecked && mutation.x() == previousX && mutation.y() == previousY && mutation.z() == previousZ) {
|
int x = mutationX[index];
|
||||||
|
int y = mutationY[index];
|
||||||
|
int z = mutationZ[index];
|
||||||
|
if (previousChecked && x == previousX && y == previousY && z == previousZ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (complex != null
|
if (complex != null
|
||||||
&& !complex.allowsNewDiscreteContentAt(mutation.x(), mutation.z())) {
|
&& !complex.allowsNewDiscreteContentAt(x, z)) {
|
||||||
discard();
|
discard();
|
||||||
return CommitResult.REJECTED_TRANSITION;
|
return CommitResult.REJECTED_TRANSITION;
|
||||||
}
|
}
|
||||||
if (!isWithinBounds(mutation.x(), mutation.y(), mutation.z())) {
|
if (!isWithinBounds(x, y, z)) {
|
||||||
discard();
|
discard();
|
||||||
return CommitResult.REJECTED_BOUNDS;
|
return CommitResult.REJECTED_BOUNDS;
|
||||||
}
|
}
|
||||||
SubterrainCell feature = engine.getSubterrainCell(mutation.x(), mutation.y(), mutation.z());
|
SubterrainCell feature = engine.getSubterrainCell(x, y, z);
|
||||||
if (SubterrainRasterizer.protectsPlacement(feature)
|
if (SubterrainRasterizer.protectsPlacement(feature)
|
||||||
|| feature != null && feature.solid() && delegate.isSolid(mutation.x(), mutation.y(), mutation.z())) {
|
|| feature != null && feature.solid() && delegate.isSolid(x, y, z)) {
|
||||||
discard();
|
discard();
|
||||||
return CommitResult.REJECTED_SUBTERRAIN;
|
return CommitResult.REJECTED_SUBTERRAIN;
|
||||||
}
|
}
|
||||||
HydrologyCaveCell hydrology = delegate.getData(
|
HydrologyCaveCell hydrology = delegate.getData(x, y, z, HydrologyCaveCell.class);
|
||||||
mutation.x(), mutation.y(), mutation.z(), HydrologyCaveCell.class);
|
|
||||||
if (hydrology != null && hydrology.protectsPlacement()) {
|
if (hydrology != null && hydrology.protectsPlacement()) {
|
||||||
discard();
|
discard();
|
||||||
return CommitResult.REJECTED_HYDROLOGY;
|
return CommitResult.REJECTED_HYDROLOGY;
|
||||||
}
|
}
|
||||||
previousChecked = true;
|
previousChecked = true;
|
||||||
previousX = mutation.x();
|
previousX = x;
|
||||||
previousY = mutation.y();
|
previousY = y;
|
||||||
previousZ = mutation.z();
|
previousZ = z;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyMutations();
|
applyMutations();
|
||||||
@@ -121,18 +132,26 @@ final class CaveObjectPlacementTransaction implements IObjectPlacer {
|
|||||||
boolean canopyOpen = false;
|
boolean canopyOpen = false;
|
||||||
boolean skipCanopy = false;
|
boolean skipCanopy = false;
|
||||||
try {
|
try {
|
||||||
for (BufferedMutation mutation : mutations) {
|
for (int index = 0; index < mutationCount; index++) {
|
||||||
if (mutation.value() instanceof NativeBlockState) {
|
int x = mutationX[index];
|
||||||
|
int y = mutationY[index];
|
||||||
|
int z = mutationZ[index];
|
||||||
|
Object value = mutationValues[index];
|
||||||
|
boolean canopy = mutationCanopies[index];
|
||||||
|
if (value instanceof NativeBlockState) {
|
||||||
if (canopyOpen) {
|
if (canopyOpen) {
|
||||||
delegate.endTreeCanopy();
|
delegate.endTreeCanopy();
|
||||||
canopyOpen = false;
|
canopyOpen = false;
|
||||||
}
|
}
|
||||||
skipCanopy = mutation.canopy()
|
skipCanopy = canopy && !delegate.beginTreeCanopy(new BlockPosition(x, y, z));
|
||||||
&& !delegate.beginTreeCanopy(new BlockPosition(mutation.x(), mutation.y(), mutation.z()));
|
canopyOpen = canopy && !skipCanopy;
|
||||||
canopyOpen = mutation.canopy() && !skipCanopy;
|
|
||||||
}
|
}
|
||||||
if (!mutation.canopy() || !skipCanopy) {
|
if (!canopy || !skipCanopy) {
|
||||||
mutation.apply(delegate);
|
switch (mutationKinds[index]) {
|
||||||
|
case BLOCK -> delegate.set(x, y, z, (NativeBlockState) value);
|
||||||
|
case TILE -> delegate.setTile(x, y, z, (TileData) value);
|
||||||
|
default -> delegate.setData(x, y, z, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -143,11 +162,31 @@ final class CaveObjectPlacementTransaction implements IObjectPlacer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void discard() {
|
void discard() {
|
||||||
mutations.clear();
|
Arrays.fill(mutationValues, 0, mutationCount, null);
|
||||||
|
mutationCount = 0;
|
||||||
bufferedBlocks.clear();
|
bufferedBlocks.clear();
|
||||||
blockWrites = 0;
|
blockWrites = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void buffer(byte kind, int x, int y, int z, Object value) {
|
||||||
|
if (mutationCount == mutationValues.length) {
|
||||||
|
int capacity = mutationCount * 2;
|
||||||
|
mutationX = Arrays.copyOf(mutationX, capacity);
|
||||||
|
mutationY = Arrays.copyOf(mutationY, capacity);
|
||||||
|
mutationZ = Arrays.copyOf(mutationZ, capacity);
|
||||||
|
mutationValues = Arrays.copyOf(mutationValues, capacity);
|
||||||
|
mutationKinds = Arrays.copyOf(mutationKinds, capacity);
|
||||||
|
mutationCanopies = Arrays.copyOf(mutationCanopies, capacity);
|
||||||
|
}
|
||||||
|
mutationX[mutationCount] = x;
|
||||||
|
mutationY[mutationCount] = y;
|
||||||
|
mutationZ[mutationCount] = z;
|
||||||
|
mutationValues[mutationCount] = value;
|
||||||
|
mutationKinds[mutationCount] = kind;
|
||||||
|
mutationCanopies[mutationCount] = isCanopy(x, y, z);
|
||||||
|
mutationCount++;
|
||||||
|
}
|
||||||
|
|
||||||
int getCaveCeiling(int x, int z) {
|
int getCaveCeiling(int x, int z) {
|
||||||
long key = Cache.key(x, z);
|
long key = Cache.key(x, z);
|
||||||
int cached = caveCeilings.get(key);
|
int cached = caveCeilings.get(key);
|
||||||
@@ -179,7 +218,7 @@ final class CaveObjectPlacementTransaction implements IObjectPlacer {
|
|||||||
if (state == null) {
|
if (state == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mutations.add(new BlockMutation(x, y, z, state, isCanopy(x, y, z)));
|
buffer(BLOCK, x, y, z, state);
|
||||||
bufferedBlocks.put(bufferKey(x, y, z), state);
|
bufferedBlocks.put(bufferKey(x, y, z), state);
|
||||||
blockWrites++;
|
blockWrites++;
|
||||||
}
|
}
|
||||||
@@ -257,7 +296,7 @@ final class CaveObjectPlacementTransaction implements IObjectPlacer {
|
|||||||
@Override
|
@Override
|
||||||
public void setTile(int x, int y, int z, TileData tile) {
|
public void setTile(int x, int y, int z, TileData tile) {
|
||||||
if (tile != null) {
|
if (tile != null) {
|
||||||
mutations.add(new TileMutation(x, y, z, tile, isCanopy(x, y, z)));
|
buffer(TILE, x, y, z, tile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +305,7 @@ final class CaveObjectPlacementTransaction implements IObjectPlacer {
|
|||||||
if (data == null) {
|
if (data == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mutations.add(new DataMutation(x, y, z, data, isCanopy(x, y, z)));
|
buffer(DATA, x, y, z, data);
|
||||||
if (data instanceof NativeBlockState state) {
|
if (data instanceof NativeBlockState state) {
|
||||||
bufferedBlocks.put(bufferKey(x, y, z), state);
|
bufferedBlocks.put(bufferKey(x, y, z), state);
|
||||||
blockWrites++;
|
blockWrites++;
|
||||||
@@ -275,12 +314,11 @@ final class CaveObjectPlacementTransaction implements IObjectPlacer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> @Nullable T getData(int x, int y, int z, Class<T> type) {
|
public <T> @Nullable T getData(int x, int y, int z, Class<T> type) {
|
||||||
for (int i = mutations.size() - 1; i >= 0; i--) {
|
for (int index = mutationCount - 1; index >= 0; index--) {
|
||||||
BufferedMutation mutation = mutations.get(i);
|
if (mutationX[index] != x || mutationY[index] != y || mutationZ[index] != z) {
|
||||||
if (mutation.x() != x || mutation.y() != y || mutation.z() != z) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Object value = mutation.value();
|
Object value = mutationValues[index];
|
||||||
if (type.isInstance(value)) {
|
if (type.isInstance(value)) {
|
||||||
return type.cast(value);
|
return type.cast(value);
|
||||||
}
|
}
|
||||||
@@ -356,40 +394,4 @@ final class CaveObjectPlacementTransaction implements IObjectPlacer {
|
|||||||
REJECTED_HYDROLOGY,
|
REJECTED_HYDROLOGY,
|
||||||
REJECTED_SUBTERRAIN
|
REJECTED_SUBTERRAIN
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface BufferedMutation {
|
|
||||||
int x();
|
|
||||||
|
|
||||||
int y();
|
|
||||||
|
|
||||||
int z();
|
|
||||||
|
|
||||||
Object value();
|
|
||||||
|
|
||||||
boolean canopy();
|
|
||||||
|
|
||||||
void apply(IObjectPlacer placer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private record BlockMutation(int x, int y, int z, NativeBlockState value, boolean canopy) implements BufferedMutation {
|
|
||||||
@Override
|
|
||||||
public void apply(IObjectPlacer placer) {
|
|
||||||
placer.set(x, y, z, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private record TileMutation(int x, int y, int z, TileData value, boolean canopy) implements BufferedMutation {
|
|
||||||
@Override
|
|
||||||
public void apply(IObjectPlacer placer) {
|
|
||||||
placer.setTile(x, y, z, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private record DataMutation(int x, int y, int z, Object value, boolean canopy) implements BufferedMutation {
|
|
||||||
@Override
|
|
||||||
public void apply(IObjectPlacer placer) {
|
|
||||||
placer.setData(x, y, z, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package art.arcane.iris.generation.mantle;
|
||||||
|
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
final class MutationLog {
|
||||||
|
private static final byte CUSTOM = 1;
|
||||||
|
private static final byte CANOPY = 2;
|
||||||
|
|
||||||
|
private int size;
|
||||||
|
private int[] x = new int[32];
|
||||||
|
private int[] y = new int[32];
|
||||||
|
private int[] z = new int[32];
|
||||||
|
private Object[] values = new Object[32];
|
||||||
|
private byte[] flags = new byte[32];
|
||||||
|
|
||||||
|
static MutationLog of(List<ObjectDestinationTransaction.Mutation> mutations) {
|
||||||
|
MutationLog log = new MutationLog();
|
||||||
|
for (ObjectDestinationTransaction.Mutation mutation : mutations) {
|
||||||
|
Objects.requireNonNull(mutation, "mutation");
|
||||||
|
ObjectDestinationTransaction.DataKey key = mutation.key();
|
||||||
|
switch (mutation) {
|
||||||
|
case ObjectDestinationTransaction.SetMutation set -> {
|
||||||
|
Object value = Objects.requireNonNull(set.value(), "mutation value");
|
||||||
|
if (ObjectSourcePlan.typeOf(value) != key.type()) {
|
||||||
|
throw new IllegalArgumentException("Object mutation type " + key.type().getName()
|
||||||
|
+ " does not match its value " + value.getClass().getName());
|
||||||
|
}
|
||||||
|
log.add(key.x(), key.y(), key.z(), value, false, set.canopy());
|
||||||
|
}
|
||||||
|
case ObjectDestinationTransaction.CustomBlockMutation block -> log.add(key.x(), key.y(), key.z(),
|
||||||
|
Objects.requireNonNull(block.state(), "custom block state"), true, block.canopy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(int blockX, int blockY, int blockZ, Object value, boolean custom, boolean canopy) {
|
||||||
|
if (size == values.length) {
|
||||||
|
int capacity = size * 2;
|
||||||
|
x = Arrays.copyOf(x, capacity);
|
||||||
|
y = Arrays.copyOf(y, capacity);
|
||||||
|
z = Arrays.copyOf(z, capacity);
|
||||||
|
values = Arrays.copyOf(values, capacity);
|
||||||
|
flags = Arrays.copyOf(flags, capacity);
|
||||||
|
}
|
||||||
|
x[size] = blockX;
|
||||||
|
y[size] = blockY;
|
||||||
|
z[size] = blockZ;
|
||||||
|
values[size] = value;
|
||||||
|
flags[size] = (byte) ((custom ? CUSTOM : 0) | (canopy ? CANOPY : 0));
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x(int index) {
|
||||||
|
return x[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
int y(int index) {
|
||||||
|
return y[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
int z(int index) {
|
||||||
|
return z[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object value(int index) {
|
||||||
|
return values[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean custom(int index) {
|
||||||
|
return (flags[index] & CUSTOM) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean canopy(int index) {
|
||||||
|
return (flags[index] & CANOPY) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> type(int index) {
|
||||||
|
return custom(index) ? NativeBlockState.class : ObjectSourcePlan.typeOf(values[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
+99
-78
@@ -8,7 +8,6 @@ import art.arcane.iris.generation.decoration.tree.TreeBlockMaterial;
|
|||||||
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveCell;
|
import art.arcane.iris.generation.hydrology.cave.HydrologyCaveCell;
|
||||||
import art.arcane.iris.generation.subterrain.SubterrainRasterizer;
|
import art.arcane.iris.generation.subterrain.SubterrainRasterizer;
|
||||||
import art.arcane.iris.generation.block.TileData;
|
import art.arcane.iris.generation.block.TileData;
|
||||||
import art.arcane.iris.structure.object.IObjectPlacer;
|
|
||||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
import art.arcane.volmlib.util.math.BlockPosition;
|
import art.arcane.volmlib.util.math.BlockPosition;
|
||||||
import art.arcane.iris.generation.block.B;
|
import art.arcane.iris.generation.block.B;
|
||||||
@@ -18,10 +17,11 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ final class ObjectDestinationTransaction implements ObjectPassPlacer {
|
|||||||
private final int destinationChunkX;
|
private final int destinationChunkX;
|
||||||
private final int destinationChunkZ;
|
private final int destinationChunkZ;
|
||||||
private final int worldHeight;
|
private final int worldHeight;
|
||||||
private final List<Mutation> mutations;
|
private final MutationLog log = new MutationLog();
|
||||||
private final List<PlacementRange> placements = new ArrayList<>();
|
private final List<PlacementRange> placements = new ArrayList<>();
|
||||||
private final Long2ObjectOpenHashMap<Int2ObjectOpenHashMap<OverlayCell>> overlay;
|
private final Long2ObjectOpenHashMap<Int2ObjectOpenHashMap<OverlayCell>> overlay;
|
||||||
private BlockPosition canopyPosition;
|
private BlockPosition canopyPosition;
|
||||||
@@ -51,7 +51,6 @@ final class ObjectDestinationTransaction implements ObjectPassPlacer {
|
|||||||
this.destinationChunkX = destinationChunkX;
|
this.destinationChunkX = destinationChunkX;
|
||||||
this.destinationChunkZ = destinationChunkZ;
|
this.destinationChunkZ = destinationChunkZ;
|
||||||
this.worldHeight = writer.getMantle().getWorldHeight();
|
this.worldHeight = writer.getMantle().getWorldHeight();
|
||||||
this.mutations = new ArrayList<>();
|
|
||||||
this.overlay = new Long2ObjectOpenHashMap<>();
|
this.overlay = new Long2ObjectOpenHashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,11 +59,11 @@ final class ObjectDestinationTransaction implements ObjectPassPlacer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int mutationCheckpoint() {
|
int mutationCheckpoint() {
|
||||||
return mutations.size();
|
return log.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectSourcePlan sourcePlanSince(int checkpoint) {
|
ObjectSourcePlan sourcePlanSince(int checkpoint) {
|
||||||
if (checkpoint < 0 || checkpoint > mutations.size()) {
|
if (checkpoint < 0 || checkpoint > log.size()) {
|
||||||
throw new IllegalArgumentException("Mutation checkpoint is outside the transaction");
|
throw new IllegalArgumentException("Mutation checkpoint is outside the transaction");
|
||||||
}
|
}
|
||||||
ArrayList<PlacementRange> accepted = new ArrayList<>();
|
ArrayList<PlacementRange> accepted = new ArrayList<>();
|
||||||
@@ -73,65 +72,91 @@ final class ObjectDestinationTransaction implements ObjectPassPlacer {
|
|||||||
accepted.add(new PlacementRange(placement.start() - checkpoint, placement.end() - checkpoint));
|
accepted.add(new PlacementRange(placement.start() - checkpoint, placement.end() - checkpoint));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new ObjectSourcePlan(mutations.subList(checkpoint, mutations.size()), accepted);
|
return new ObjectSourcePlan(log, checkpoint, accepted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int beginObjectPlacement() {
|
public int beginObjectPlacement() {
|
||||||
return mutations.size();
|
return log.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endObjectPlacement(int checkpoint) {
|
public void endObjectPlacement(int checkpoint) {
|
||||||
if (checkpoint >= 0 && checkpoint < mutations.size()) {
|
if (checkpoint >= 0 && checkpoint < log.size()) {
|
||||||
placements.add(new PlacementRange(checkpoint, mutations.size()));
|
placements.add(new PlacementRange(checkpoint, log.size()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void apply(ObjectSourcePlan plan) {
|
void apply(ObjectSourcePlan plan) {
|
||||||
|
int slot = plan.destinationSlot(destinationChunkX, destinationChunkZ);
|
||||||
|
if (slot < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int baseX = destinationChunkX << 4;
|
||||||
|
int baseZ = destinationChunkZ << 4;
|
||||||
boolean skipCanopy = false;
|
boolean skipCanopy = false;
|
||||||
for (Mutation mutation : plan.mutationsFor(destinationChunkX, destinationChunkZ)) {
|
int end = plan.slotEnd(slot);
|
||||||
if (mutation.key().type() == NativeBlockState.class) {
|
for (int index = plan.slotStart(slot); index < end; index++) {
|
||||||
NativeBlockState existing = mutation.canopy() ? get(mutation.key().x(), mutation.key().y(), mutation.key().z()) : null;
|
int cell = plan.cellAt(index);
|
||||||
|
int x = baseX | ObjectSourcePlan.cellX(cell);
|
||||||
|
int y = ObjectSourcePlan.cellY(cell);
|
||||||
|
int z = baseZ | ObjectSourcePlan.cellZ(cell);
|
||||||
|
Object value = plan.valueAt(index);
|
||||||
|
boolean custom = ObjectSourcePlan.isCustom(cell);
|
||||||
|
boolean canopy = ObjectSourcePlan.isCanopy(cell);
|
||||||
|
if (custom || value instanceof NativeBlockState) {
|
||||||
|
NativeBlockState existing = canopy ? get(x, y, z) : null;
|
||||||
skipCanopy = existing != null && existing.isSolid();
|
skipCanopy = existing != null && existing.isSolid();
|
||||||
}
|
}
|
||||||
if (mutation.canopy() && skipCanopy) {
|
if (canopy && skipCanopy) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
switch (mutation) {
|
if (custom) {
|
||||||
case SetMutation set -> setData(set.key().x(), set.key().y(), set.key().z(), set.value(), set);
|
set(x, y, z, (NativeBlockState) value, true, canopy);
|
||||||
case CustomBlockMutation custom -> set(custom.key().x(), custom.key().y(), custom.key().z(), custom.state(), custom);
|
} else {
|
||||||
|
setData(x, y, z, value, true, canopy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void commitFenced() {
|
private void commitFenced() {
|
||||||
LinkedHashMap<DataKey, Object> originals = new LinkedHashMap<>();
|
CommitJournal journal = new CommitJournal();
|
||||||
try {
|
try {
|
||||||
for (Mutation mutation : mutations) {
|
for (int position = 0; position < log.size(); position++) {
|
||||||
if (!isDestination(mutation.x(), mutation.z())) {
|
int x = log.x(position);
|
||||||
|
int z = log.z(position);
|
||||||
|
if (!isDestination(x, z)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
captureOriginal(originals, mutation.key());
|
int y = log.y(position);
|
||||||
if (mutation.key().type() == NativeBlockState.class) {
|
Object value = log.value(position);
|
||||||
TileWrapper tile = writer.getDataIfPresent(
|
boolean custom = log.custom(position);
|
||||||
mutation.key().x(), mutation.key().y(), mutation.key().z(), TileWrapper.class);
|
Class<?> type = log.type(position);
|
||||||
if (tile != null) {
|
if (!journal.captured(x, y, z, type)) {
|
||||||
originals.putIfAbsent(new DataKey(
|
journal.capture(x, y, z, type, publishedOrCleared(x, y, z, type));
|
||||||
mutation.key().x(), mutation.key().y(), mutation.key().z(), TileWrapper.class), tile);
|
}
|
||||||
|
if (type == NativeBlockState.class) {
|
||||||
|
if (!journal.captured(x, y, z, TileWrapper.class)) {
|
||||||
|
TileWrapper tile = writer.getDataIfPresent(x, y, z, TileWrapper.class);
|
||||||
|
if (tile != null) {
|
||||||
|
journal.capture(x, y, z, TileWrapper.class, tile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Identifier identifier = writer.getDataIfPresent(
|
if (!journal.captured(x, y, z, Identifier.class)) {
|
||||||
mutation.key().x(), mutation.key().y(), mutation.key().z(), Identifier.class);
|
Identifier identifier = writer.getDataIfPresent(x, y, z, Identifier.class);
|
||||||
if (identifier != null || mutation instanceof CustomBlockMutation) {
|
if (identifier != null || custom) {
|
||||||
originals.putIfAbsent(new DataKey(
|
journal.capture(x, y, z, Identifier.class, identifier == null ? CLEARED : identifier);
|
||||||
mutation.key().x(), mutation.key().y(), mutation.key().z(), Identifier.class),
|
}
|
||||||
identifier == null ? CLEARED : identifier);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mutation.apply(writer);
|
if (custom) {
|
||||||
|
writer.set(x, y, z, (NativeBlockState) value);
|
||||||
|
} else {
|
||||||
|
writer.setData(x, y, z, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable failure) {
|
} catch (Throwable failure) {
|
||||||
rollback(originals, failure);
|
journal.rollback(failure);
|
||||||
if (failure instanceof RuntimeException runtimeException) {
|
if (failure instanceof RuntimeException runtimeException) {
|
||||||
throw runtimeException;
|
throw runtimeException;
|
||||||
}
|
}
|
||||||
@@ -154,10 +179,10 @@ final class ObjectDestinationTransaction implements ObjectPassPlacer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void set(int x, int y, int z, NativeBlockState state) {
|
public void set(int x, int y, int z, NativeBlockState state) {
|
||||||
set(x, y, z, state, null);
|
set(x, y, z, state, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void set(int x, int y, int z, NativeBlockState state, CustomBlockMutation replayed) {
|
private void set(int x, int y, int z, NativeBlockState state, boolean replayed, boolean replayedCanopy) {
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -172,8 +197,7 @@ final class ObjectDestinationTransaction implements ObjectPassPlacer {
|
|||||||
cell.block = baseState;
|
cell.block = baseState;
|
||||||
cell.tile = CLEARED;
|
cell.tile = CLEARED;
|
||||||
cell.identifier = identifier;
|
cell.identifier = identifier;
|
||||||
mutations.add(replayed != null
|
log.add(x, y, z, state, true, replayed ? replayedCanopy : isCanopy(x, y, z));
|
||||||
? replayed : new CustomBlockMutation(new DataKey(x, y, z, NativeBlockState.class), state, isCanopy(x, y, z)));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setData(x, y, z, state);
|
setData(x, y, z, state);
|
||||||
@@ -267,10 +291,10 @@ final class ObjectDestinationTransaction implements ObjectPassPlacer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> void setData(int x, int y, int z, T data) {
|
public <T> void setData(int x, int y, int z, T data) {
|
||||||
setData(x, y, z, data, null);
|
setData(x, y, z, data, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setData(int x, int y, int z, Object data, SetMutation replayed) {
|
private void setData(int x, int y, int z, Object data, boolean replayed, boolean replayedCanopy) {
|
||||||
if (data == null || y < 0 || y >= worldHeight
|
if (data == null || y < 0 || y >= worldHeight
|
||||||
|| protectsSubterrain(x, y, z)) {
|
|| protectsSubterrain(x, y, z)) {
|
||||||
return;
|
return;
|
||||||
@@ -290,8 +314,7 @@ final class ObjectDestinationTransaction implements ObjectPassPlacer {
|
|||||||
} else {
|
} else {
|
||||||
cell.put(type, data);
|
cell.put(type, data);
|
||||||
}
|
}
|
||||||
mutations.add(replayed != null
|
log.add(x, y, z, data, false, replayed ? replayedCanopy : isCanopy(x, y, z));
|
||||||
? replayed : new SetMutation(new DataKey(x, y, z, type), data, isCanopy(x, y, z)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -373,18 +396,6 @@ final class ObjectDestinationTransaction implements ObjectPassPlacer {
|
|||||||
return value == null ? CLEARED : value;
|
return value == null ? CLEARED : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void captureOriginal(LinkedHashMap<DataKey, Object> originals, DataKey key) {
|
|
||||||
originals.computeIfAbsent(
|
|
||||||
key,
|
|
||||||
candidate -> publishedOrCleared(
|
|
||||||
candidate.x(),
|
|
||||||
candidate.y(),
|
|
||||||
candidate.z(),
|
|
||||||
candidate.type()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isDestination(int x, int z) {
|
private boolean isDestination(int x, int z) {
|
||||||
return (x >> 4) == destinationChunkX && (z >> 4) == destinationChunkZ;
|
return (x >> 4) == destinationChunkX && (z >> 4) == destinationChunkZ;
|
||||||
}
|
}
|
||||||
@@ -433,22 +444,44 @@ final class ObjectDestinationTransaction implements ObjectPassPlacer {
|
|||||||
return hydrologyGateOpen;
|
return hydrologyGateOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rollback(LinkedHashMap<DataKey, Object> originals, Throwable failure) {
|
private final class CommitJournal {
|
||||||
ArrayList<Map.Entry<DataKey, Object>> entries = new ArrayList<>(originals.entrySet());
|
private final Reference2IntOpenHashMap<Class<?>> typeIds = new Reference2IntOpenHashMap<>();
|
||||||
for (int index = entries.size() - 1; index >= 0; index--) {
|
private final LongOpenHashSet keys = new LongOpenHashSet();
|
||||||
Map.Entry<DataKey, Object> entry = entries.get(index);
|
private final ArrayList<Original> originals = new ArrayList<>();
|
||||||
DataKey key = entry.getKey();
|
|
||||||
try {
|
private long key(int x, int y, int z, Class<?> type) {
|
||||||
writer.restoreData(key.x(), key.y(), key.z(), key.type(),
|
int typeId = typeIds.computeIfAbsent(type, (Class<?> ignored) -> typeIds.size());
|
||||||
entry.getValue() == CLEARED ? null : entry.getValue());
|
return ((long) typeId << 32) | ((long) y << 8) | ((long) (z & 15) << 4) | (x & 15);
|
||||||
} catch (Throwable rollbackFailure) {
|
}
|
||||||
if (rollbackFailure != failure) {
|
|
||||||
failure.addSuppressed(rollbackFailure);
|
private boolean captured(int x, int y, int z, Class<?> type) {
|
||||||
|
return keys.contains(key(x, y, z, type));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void capture(int x, int y, int z, Class<?> type, Object value) {
|
||||||
|
if (keys.add(key(x, y, z, type))) {
|
||||||
|
originals.add(new Original(x, y, z, type, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rollback(Throwable failure) {
|
||||||
|
for (int index = originals.size() - 1; index >= 0; index--) {
|
||||||
|
Original original = originals.get(index);
|
||||||
|
try {
|
||||||
|
writer.restoreData(original.x(), original.y(), original.z(), original.type(),
|
||||||
|
original.value() == CLEARED ? null : original.value());
|
||||||
|
} catch (Throwable rollbackFailure) {
|
||||||
|
if (rollbackFailure != failure) {
|
||||||
|
failure.addSuppressed(rollbackFailure);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record Original(int x, int y, int z, Class<?> type, Object value) {
|
||||||
|
}
|
||||||
|
|
||||||
private static final class OverlayCell {
|
private static final class OverlayCell {
|
||||||
private Object block;
|
private Object block;
|
||||||
private Object hydrology;
|
private Object hydrology;
|
||||||
@@ -520,8 +553,6 @@ final class ObjectDestinationTransaction implements ObjectPassPlacer {
|
|||||||
DataKey key();
|
DataKey key();
|
||||||
|
|
||||||
boolean canopy();
|
boolean canopy();
|
||||||
|
|
||||||
void apply(IObjectPlacer placer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
record SetMutation(DataKey key, Object value, boolean canopy) implements Mutation {
|
record SetMutation(DataKey key, Object value, boolean canopy) implements Mutation {
|
||||||
@@ -534,11 +565,6 @@ final class ObjectDestinationTransaction implements ObjectPassPlacer {
|
|||||||
public int z() {
|
public int z() {
|
||||||
return key.z();
|
return key.z();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(IObjectPlacer placer) {
|
|
||||||
placer.setData(key.x(), key.y(), key.z(), value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
record CustomBlockMutation(DataKey key, NativeBlockState state, boolean canopy) implements Mutation {
|
record CustomBlockMutation(DataKey key, NativeBlockState state, boolean canopy) implements Mutation {
|
||||||
@@ -551,10 +577,5 @@ final class ObjectDestinationTransaction implements ObjectPassPlacer {
|
|||||||
public int z() {
|
public int z() {
|
||||||
return key.z();
|
return key.z();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(IObjectPlacer placer) {
|
|
||||||
placer.set(key.x(), key.y(), key.z(), state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,6 @@ import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
final class ObjectSourcePlan {
|
final class ObjectSourcePlan {
|
||||||
static final ObjectSourcePlan EMPTY = new ObjectSourcePlan(List.of());
|
static final ObjectSourcePlan EMPTY = new ObjectSourcePlan(List.of());
|
||||||
@@ -45,18 +44,21 @@ final class ObjectSourcePlan {
|
|||||||
private final int estimatedRetainedBytes;
|
private final int estimatedRetainedBytes;
|
||||||
|
|
||||||
ObjectSourcePlan(List<ObjectDestinationTransaction.Mutation> mutations) {
|
ObjectSourcePlan(List<ObjectDestinationTransaction.Mutation> mutations) {
|
||||||
this(mutations, List.of());
|
this(MutationLog.of(mutations), 0, List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectSourcePlan(List<ObjectDestinationTransaction.Mutation> mutations, List<ObjectDestinationTransaction.PlacementRange> accepted) {
|
ObjectSourcePlan(List<ObjectDestinationTransaction.Mutation> mutations, List<ObjectDestinationTransaction.PlacementRange> accepted) {
|
||||||
int count = mutations.size();
|
this(MutationLog.of(mutations), 0, accepted);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectSourcePlan(MutationLog log, int from, List<ObjectDestinationTransaction.PlacementRange> accepted) {
|
||||||
|
int count = log.size() - from;
|
||||||
long[] mutationDestinations = new long[count];
|
long[] mutationDestinations = new long[count];
|
||||||
Long2IntOpenHashMap destinationSlots = new Long2IntOpenHashMap();
|
Long2IntOpenHashMap destinationSlots = new Long2IntOpenHashMap();
|
||||||
destinationSlots.defaultReturnValue(-1);
|
destinationSlots.defaultReturnValue(-1);
|
||||||
LongArrayList firstSeen = new LongArrayList();
|
LongArrayList firstSeen = new LongArrayList();
|
||||||
for (int index = 0; index < count; index++) {
|
for (int index = 0; index < count; index++) {
|
||||||
ObjectDestinationTransaction.Mutation mutation = Objects.requireNonNull(mutations.get(index), "mutation");
|
long destination = destinationKey(log.x(from + index) >> 4, log.z(from + index) >> 4);
|
||||||
long destination = destinationKey(mutation.x() >> 4, mutation.z() >> 4);
|
|
||||||
mutationDestinations[index] = destination;
|
mutationDestinations[index] = destination;
|
||||||
if (destinationSlots.putIfAbsent(destination, firstSeen.size()) == -1) {
|
if (destinationSlots.putIfAbsent(destination, firstSeen.size()) == -1) {
|
||||||
firstSeen.add(destination);
|
firstSeen.add(destination);
|
||||||
@@ -80,38 +82,23 @@ final class ObjectSourcePlan {
|
|||||||
valueIndexes = new int[count];
|
valueIndexes = new int[count];
|
||||||
ValueTable table = new ValueTable();
|
ValueTable table = new ValueTable();
|
||||||
for (int index = 0; index < count; index++) {
|
for (int index = 0; index < count; index++) {
|
||||||
ObjectDestinationTransaction.Mutation mutation = mutations.get(index);
|
int logged = from + index;
|
||||||
int position = cursor[destinationSlots.get(mutationDestinations[index])]++;
|
int position = cursor[destinationSlots.get(mutationDestinations[index])]++;
|
||||||
positions[index] = position;
|
positions[index] = position;
|
||||||
ObjectDestinationTransaction.DataKey key = mutation.key();
|
int y = log.y(logged);
|
||||||
if (key.y() < 0 || key.y() > MAXIMUM_Y) {
|
if (y < 0 || y > MAXIMUM_Y) {
|
||||||
throw new IllegalArgumentException("Object mutation height is outside the plan range: " + key.y());
|
throw new IllegalArgumentException("Object mutation height is outside the plan range: " + y);
|
||||||
}
|
}
|
||||||
Object value;
|
cells[position] = (y << Y_SHIFT) | (log.custom(logged) ? CUSTOM_BIT : 0) | (log.canopy(logged) ? CANOPY_BIT : 0)
|
||||||
int custom;
|
| ((log.z(logged) & 15) << 4) | (log.x(logged) & 15);
|
||||||
switch (mutation) {
|
valueIndexes[position] = table.indexOf(log.value(logged));
|
||||||
case ObjectDestinationTransaction.SetMutation set -> {
|
|
||||||
value = Objects.requireNonNull(set.value(), "mutation value");
|
|
||||||
if (typeOf(value) != key.type()) {
|
|
||||||
throw new IllegalArgumentException("Object mutation type " + key.type().getName()
|
|
||||||
+ " does not match its value " + value.getClass().getName());
|
|
||||||
}
|
|
||||||
custom = 0;
|
|
||||||
}
|
|
||||||
case ObjectDestinationTransaction.CustomBlockMutation block -> {
|
|
||||||
value = Objects.requireNonNull(block.state(), "custom block state");
|
|
||||||
custom = CUSTOM_BIT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cells[position] = (key.y() << Y_SHIFT) | custom | (mutation.canopy() ? CANOPY_BIT : 0) | ((key.z() & 15) << 4) | (key.x() & 15);
|
|
||||||
valueIndexes[position] = table.indexOf(value);
|
|
||||||
}
|
}
|
||||||
values = table.values.toArray();
|
values = table.values.toArray();
|
||||||
|
|
||||||
ArrayList<Placement> crossing = new ArrayList<>();
|
ArrayList<Placement> crossing = new ArrayList<>();
|
||||||
for (int ordinal = 0; ordinal < accepted.size(); ordinal++) {
|
for (int ordinal = 0; ordinal < accepted.size(); ordinal++) {
|
||||||
ObjectDestinationTransaction.PlacementRange range = accepted.get(ordinal);
|
ObjectDestinationTransaction.PlacementRange range = accepted.get(ordinal);
|
||||||
Placement placement = crossingPlacement(ordinal, range.start(), range.end(), mutations, mutationDestinations, destinationSlots, positions);
|
Placement placement = crossingPlacement(ordinal, range.start(), range.end(), log, from, mutationDestinations, destinationSlots, positions);
|
||||||
if (placement != null) {
|
if (placement != null) {
|
||||||
crossing.add(placement);
|
crossing.add(placement);
|
||||||
}
|
}
|
||||||
@@ -147,12 +134,13 @@ final class ObjectSourcePlan {
|
|||||||
int ordinal,
|
int ordinal,
|
||||||
int start,
|
int start,
|
||||||
int end,
|
int end,
|
||||||
List<ObjectDestinationTransaction.Mutation> mutations,
|
MutationLog log,
|
||||||
|
int from,
|
||||||
long[] mutationDestinations,
|
long[] mutationDestinations,
|
||||||
Long2IntOpenHashMap destinationSlots,
|
Long2IntOpenHashMap destinationSlots,
|
||||||
int[] positions
|
int[] positions
|
||||||
) {
|
) {
|
||||||
if (start < 0 || end > mutations.size() || start >= end) {
|
if (start < 0 || end > log.size() - from || start >= end) {
|
||||||
throw new IllegalArgumentException("Object placement range is outside the plan");
|
throw new IllegalArgumentException("Object placement range is outside the plan");
|
||||||
}
|
}
|
||||||
long first = mutationDestinations[start];
|
long first = mutationDestinations[start];
|
||||||
@@ -172,11 +160,12 @@ final class ObjectSourcePlan {
|
|||||||
int maximumX = Integer.MIN_VALUE;
|
int maximumX = Integer.MIN_VALUE;
|
||||||
int maximumZ = Integer.MIN_VALUE;
|
int maximumZ = Integer.MIN_VALUE;
|
||||||
for (int mutationIndex = start; mutationIndex < end; mutationIndex++) {
|
for (int mutationIndex = start; mutationIndex < end; mutationIndex++) {
|
||||||
ObjectDestinationTransaction.Mutation mutation = mutations.get(mutationIndex);
|
int mutationX = log.x(from + mutationIndex);
|
||||||
minimumX = Math.min(minimumX, mutation.x());
|
int mutationZ = log.z(from + mutationIndex);
|
||||||
minimumZ = Math.min(minimumZ, mutation.z());
|
minimumX = Math.min(minimumX, mutationX);
|
||||||
maximumX = Math.max(maximumX, mutation.x());
|
minimumZ = Math.min(minimumZ, mutationZ);
|
||||||
maximumZ = Math.max(maximumZ, mutation.z());
|
maximumX = Math.max(maximumX, mutationX);
|
||||||
|
maximumZ = Math.max(maximumZ, mutationZ);
|
||||||
long destination = mutationDestinations[mutationIndex];
|
long destination = mutationDestinations[mutationIndex];
|
||||||
int touchedIndex = touchedIndexes.get(destination);
|
int touchedIndex = touchedIndexes.get(destination);
|
||||||
if (touchedIndex < 0) {
|
if (touchedIndex < 0) {
|
||||||
@@ -216,6 +205,55 @@ final class ObjectSourcePlan {
|
|||||||
return destinations.length == 0;
|
return destinations.length == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int slotStart(int slot) {
|
||||||
|
return offsets[slot];
|
||||||
|
}
|
||||||
|
|
||||||
|
int slotEnd(int slot) {
|
||||||
|
return offsets[slot + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
int cellAt(int index) {
|
||||||
|
return cells[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object valueAt(int index) {
|
||||||
|
return values[valueIndexes[index]];
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cellX(int cell) {
|
||||||
|
return cell & 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cellZ(int cell) {
|
||||||
|
return (cell >>> 4) & 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cellY(int cell) {
|
||||||
|
return cell >>> Y_SHIFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isCustom(int cell) {
|
||||||
|
return (cell & CUSTOM_BIT) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isCanopy(int cell) {
|
||||||
|
return (cell & CANOPY_BIT) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectDestinationTransaction.Mutation mutationAt(int chunkX, int chunkZ, int index) {
|
||||||
|
int cell = cells[index];
|
||||||
|
int x = (chunkX << 4) | cellX(cell);
|
||||||
|
int z = (chunkZ << 4) | cellZ(cell);
|
||||||
|
int y = cellY(cell);
|
||||||
|
Object value = valueAt(index);
|
||||||
|
return isCustom(cell)
|
||||||
|
? new ObjectDestinationTransaction.CustomBlockMutation(
|
||||||
|
new ObjectDestinationTransaction.DataKey(x, y, z, NativeBlockState.class), (NativeBlockState) value, isCanopy(cell))
|
||||||
|
: new ObjectDestinationTransaction.SetMutation(
|
||||||
|
new ObjectDestinationTransaction.DataKey(x, y, z, typeOf(value)), value, isCanopy(cell));
|
||||||
|
}
|
||||||
|
|
||||||
List<ObjectDestinationTransaction.Mutation> mutationsFor(int chunkX, int chunkZ) {
|
List<ObjectDestinationTransaction.Mutation> mutationsFor(int chunkX, int chunkZ) {
|
||||||
int slot = destinationSlot(chunkX, chunkZ);
|
int slot = destinationSlot(chunkX, chunkZ);
|
||||||
return slot < 0 ? List.of() : decode(chunkX, chunkZ, offsets[slot], offsets[slot + 1] - offsets[slot]);
|
return slot < 0 ? List.of() : decode(chunkX, chunkZ, offsets[slot], offsets[slot + 1] - offsets[slot]);
|
||||||
@@ -266,19 +304,8 @@ final class ObjectSourcePlan {
|
|||||||
|
|
||||||
private List<ObjectDestinationTransaction.Mutation> decode(int chunkX, int chunkZ, int start, int length) {
|
private List<ObjectDestinationTransaction.Mutation> decode(int chunkX, int chunkZ, int start, int length) {
|
||||||
ObjectDestinationTransaction.Mutation[] decoded = new ObjectDestinationTransaction.Mutation[length];
|
ObjectDestinationTransaction.Mutation[] decoded = new ObjectDestinationTransaction.Mutation[length];
|
||||||
int baseX = chunkX << 4;
|
|
||||||
int baseZ = chunkZ << 4;
|
|
||||||
for (int index = 0; index < length; index++) {
|
for (int index = 0; index < length; index++) {
|
||||||
int cell = cells[start + index];
|
decoded[index] = mutationAt(chunkX, chunkZ, start + index);
|
||||||
int x = baseX | (cell & 15);
|
|
||||||
int z = baseZ | ((cell >>> 4) & 15);
|
|
||||||
int y = cell >>> Y_SHIFT;
|
|
||||||
Object value = values[valueIndexes[start + index]];
|
|
||||||
decoded[index] = (cell & CUSTOM_BIT) != 0
|
|
||||||
? new ObjectDestinationTransaction.CustomBlockMutation(
|
|
||||||
new ObjectDestinationTransaction.DataKey(x, y, z, NativeBlockState.class), (NativeBlockState) value, (cell & CANOPY_BIT) != 0)
|
|
||||||
: new ObjectDestinationTransaction.SetMutation(
|
|
||||||
new ObjectDestinationTransaction.DataKey(x, y, z, typeOf(value)), value, (cell & CANOPY_BIT) != 0);
|
|
||||||
}
|
}
|
||||||
return List.of(decoded);
|
return List.of(decoded);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import art.arcane.iris.generation.decoration.IrisVacuumSettings;
|
|||||||
import art.arcane.iris.world.entity.IrisMarker;
|
import art.arcane.iris.world.entity.IrisMarker;
|
||||||
|
|
||||||
import art.arcane.iris.pack.loading.IrisData;
|
import art.arcane.iris.pack.loading.IrisData;
|
||||||
import art.arcane.iris.generation.runtime.GenerationFailures;
|
|
||||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||||
import art.arcane.iris.generation.runtime.Engine;
|
import art.arcane.iris.generation.runtime.Engine;
|
||||||
import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
|
import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package art.arcane.iris.generation.mantle;
|
||||||
|
|
||||||
|
import art.arcane.iris.testsupport.KeyedBlockState;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class MutationLogTest {
|
||||||
|
@Test
|
||||||
|
public void logKeepsPositionsValuesAndFlagsInOrder() {
|
||||||
|
NativeBlockState state = new KeyedBlockState("minecraft:stone");
|
||||||
|
MutationLog log = MutationLog.of(List.of(
|
||||||
|
new ObjectDestinationTransaction.SetMutation(new ObjectDestinationTransaction.DataKey(3, 70, -2, String.class), "marker", true),
|
||||||
|
new ObjectDestinationTransaction.CustomBlockMutation(new ObjectDestinationTransaction.DataKey(-17, 4, 40, NativeBlockState.class), state, false)));
|
||||||
|
for (int index = 0; index < 100; index++) {
|
||||||
|
log.add(index, index + 1, -index, "grow" + index, (index & 1) == 0, (index & 2) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(102, log.size());
|
||||||
|
assertEquals(3, log.x(0));
|
||||||
|
assertEquals(70, log.y(0));
|
||||||
|
assertEquals(-2, log.z(0));
|
||||||
|
assertEquals("marker", log.value(0));
|
||||||
|
assertFalse(log.custom(0));
|
||||||
|
assertTrue(log.canopy(0));
|
||||||
|
assertSame(String.class, log.type(0));
|
||||||
|
assertSame(state, log.value(1));
|
||||||
|
assertTrue(log.custom(1));
|
||||||
|
assertFalse(log.canopy(1));
|
||||||
|
assertSame(NativeBlockState.class, log.type(1));
|
||||||
|
assertEquals(99, log.x(101));
|
||||||
|
assertEquals("grow99", log.value(101));
|
||||||
|
assertFalse(log.custom(101));
|
||||||
|
assertTrue(log.canopy(98));
|
||||||
|
assertFalse(log.canopy(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mismatchedMutationTypesAreRejected() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> MutationLog.of(List.of(
|
||||||
|
new ObjectDestinationTransaction.SetMutation(new ObjectDestinationTransaction.DataKey(0, 4, 0, Integer.class), "text", false))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void planSlicesMatchThePlanBuiltFromTheSameMutations() {
|
||||||
|
MutationLog log = new MutationLog();
|
||||||
|
List<ObjectDestinationTransaction.Mutation> tail = new ArrayList<>();
|
||||||
|
for (int index = 0; index < 64; index++) {
|
||||||
|
int x = (index * 7) - 30;
|
||||||
|
int y = 4 + index;
|
||||||
|
int z = (index * 5) - 20;
|
||||||
|
String value = "value" + (index % 9);
|
||||||
|
boolean canopy = index % 4 == 0;
|
||||||
|
log.add(x, y, z, value, false, canopy);
|
||||||
|
if (index >= 10) {
|
||||||
|
tail.add(new ObjectDestinationTransaction.SetMutation(new ObjectDestinationTransaction.DataKey(x, y, z, String.class), value, canopy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ObjectSourcePlan sliced = new ObjectSourcePlan(log, 10, List.of());
|
||||||
|
ObjectSourcePlan expected = new ObjectSourcePlan(tail);
|
||||||
|
|
||||||
|
assertEquals(expected.destinationCount(), sliced.destinationCount());
|
||||||
|
int total = 0;
|
||||||
|
for (int chunkX = -3; chunkX <= 30; chunkX++) {
|
||||||
|
for (int chunkZ = -3; chunkZ <= 30; chunkZ++) {
|
||||||
|
List<ObjectDestinationTransaction.Mutation> mutations = sliced.mutationsFor(chunkX, chunkZ);
|
||||||
|
assertEquals(expected.mutationsFor(chunkX, chunkZ), mutations);
|
||||||
|
total += mutations.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(54, total);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user