mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +00:00
Memoize object block states
- Cache vanilla withProperty variants per state - Cache rotated states per rotation and quantized spin - Hoist the stilt dirt state and rotate only target-layer stilt blocks
This commit is contained in:
1 parent
ec07f55fd8
commit
2f8d3b1d1a
6 files changed
+115
-7
No files matched your search
@@ -80,6 +80,7 @@ public final class BukkitBlockState implements NativeBlockState {
|
|||||||
private volatile Boolean deepSlate;
|
private volatile Boolean deepSlate;
|
||||||
private volatile Boolean vineBlock;
|
private volatile Boolean vineBlock;
|
||||||
private volatile Boolean tileEntity;
|
private volatile Boolean tileEntity;
|
||||||
|
private volatile ConcurrentHashMap<String, ConcurrentHashMap<String, NativeBlockState>> propertyVariants;
|
||||||
|
|
||||||
private BukkitBlockState(BlockData data, String key) {
|
private BukkitBlockState(BlockData data, String key) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
@@ -428,7 +429,18 @@ public final class BukkitBlockState implements NativeBlockState {
|
|||||||
BlockData resolved = Bukkit.createBlockData(merged);
|
BlockData resolved = Bukkit.createBlockData(merged);
|
||||||
return of(IrisCustomData.of(resolved, custom.getCustom()));
|
return of(IrisCustomData.of(resolved, custom.getCustom()));
|
||||||
}
|
}
|
||||||
return of(Bukkit.createBlockData(BlockStateKey.withProperty(key, name, value)));
|
ConcurrentHashMap<String, ConcurrentHashMap<String, NativeBlockState>> variants = propertyVariants;
|
||||||
|
if (variants == null) {
|
||||||
|
variants = new ConcurrentHashMap<>(4);
|
||||||
|
propertyVariants = variants;
|
||||||
|
}
|
||||||
|
ConcurrentHashMap<String, NativeBlockState> values = variants.computeIfAbsent(name, (String ignored) -> new ConcurrentHashMap<>(4));
|
||||||
|
NativeBlockState variant = values.get(value);
|
||||||
|
if (variant == null) {
|
||||||
|
variant = of(Bukkit.createBlockData(BlockStateKey.withProperty(key, name, value)));
|
||||||
|
values.putIfAbsent(value, variant);
|
||||||
|
}
|
||||||
|
return variant;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
NativeBlockState stone,
|
NativeBlockState stone,
|
||||||
NativeBlockState vair,
|
NativeBlockState vair,
|
||||||
NativeBlockState vairDebug,
|
NativeBlockState vairDebug,
|
||||||
|
NativeBlockState dirt,
|
||||||
NativeBlockState[] snowLayers) {
|
NativeBlockState[] snowLayers) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +103,7 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
B.getState("STONE"),
|
B.getState("STONE"),
|
||||||
B.getState("VOID_AIR"),
|
B.getState("VOID_AIR"),
|
||||||
B.getState("COBWEB"),
|
B.getState("COBWEB"),
|
||||||
|
B.getState("minecraft:dirt"),
|
||||||
new NativeBlockState[]{B.getState("minecraft:snow[layers=1]"), B.getState("minecraft:snow[layers=2]"), B.getState("minecraft:snow[layers=3]"), B.getState("minecraft:snow[layers=4]"), B.getState("minecraft:snow[layers=5]"), B.getState("minecraft:snow[layers=6]"), B.getState("minecraft:snow[layers=7]"), B.getState("minecraft:snow[layers=8]")});
|
new NativeBlockState[]{B.getState("minecraft:snow[layers=1]"), B.getState("minecraft:snow[layers=2]"), B.getState("minecraft:snow[layers=3]"), B.getState("minecraft:snow[layers=4]"), B.getState("minecraft:snow[layers=5]"), B.getState("minecraft:snow[layers=6]"), B.getState("minecraft:snow[layers=7]"), B.getState("minecraft:snow[layers=8]")});
|
||||||
bound = resolved;
|
bound = resolved;
|
||||||
return resolved;
|
return resolved;
|
||||||
@@ -123,6 +125,10 @@ public class IrisObject extends IrisRegistrant {
|
|||||||
return bound().vairDebug();
|
return bound().vairDebug();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static NativeBlockState dirt() {
|
||||||
|
return bound().dirt();
|
||||||
|
}
|
||||||
|
|
||||||
static NativeBlockState snowLayer(int layerIndex) {
|
static NativeBlockState snowLayer(int layerIndex) {
|
||||||
return bound().snowLayers()[layerIndex];
|
return bound().snowLayers()[layerIndex];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -733,7 +733,7 @@ final class IrisObjectPlacementRunner {
|
|||||||
} else {
|
} else {
|
||||||
String mat = IrisObjectShaping.materialKey(d);
|
String mat = IrisObjectShaping.materialKey(d);
|
||||||
if (mat.equals("minecraft:grass_block") || mat.equals("minecraft:mycelium") || mat.equals("minecraft:podzol") || mat.equals("minecraft:dirt_path")) {
|
if (mat.equals("minecraft:grass_block") || mat.equals("minecraft:mycelium") || mat.equals("minecraft:podzol") || mat.equals("minecraft:dirt_path")) {
|
||||||
d = B.getState("minecraft:dirt");
|
d = IrisObject.States.dirt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -745,11 +745,10 @@ final class IrisObjectPlacementRunner {
|
|||||||
if (translating) {
|
if (translating) {
|
||||||
i.add(translateOffset);
|
i.add(translateOffset);
|
||||||
}
|
}
|
||||||
d = config.getRotation().rotate(d, spinx, spiny, spinz);
|
|
||||||
|
|
||||||
int targetLayer = ceilingHang ? topLayer : lowest;
|
int targetLayer = ceilingHang ? topLayer : lowest;
|
||||||
if (i.getBlockY() != targetLayer)
|
if (i.getBlockY() != targetLayer)
|
||||||
continue;
|
continue;
|
||||||
|
d = config.getRotation().rotate(d, spinx, spiny, spinz);
|
||||||
|
|
||||||
if (hasEdits) {
|
if (hasEdits) {
|
||||||
for (IrisObjectReplace j : config.getEdit()) {
|
for (IrisObjectReplace j : config.getEdit()) {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
@Snippet("object-rotator")
|
@Snippet("object-rotator")
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
@@ -59,6 +60,8 @@ import java.util.Set;
|
|||||||
@Data
|
@Data
|
||||||
public class IrisObjectRotation {
|
public class IrisObjectRotation {
|
||||||
private static volatile StateRotator PLATFORM_ROTATOR = null;
|
private static volatile StateRotator PLATFORM_ROTATOR = null;
|
||||||
|
private static final Object ROTATED_AWAY = new Object();
|
||||||
|
private static final int SPIN_STEPS = 5;
|
||||||
|
|
||||||
public interface StateRotator {
|
public interface StateRotator {
|
||||||
NativeBlockState rotate(IrisObjectRotation rotation, NativeBlockState state, int spinx, int spiny, int spinz);
|
NativeBlockState rotate(IrisObjectRotation rotation, NativeBlockState state, int spinx, int spiny, int spinz);
|
||||||
@@ -91,6 +94,8 @@ public class IrisObjectRotation {
|
|||||||
@Description("The z axis rotation")
|
@Description("The z axis rotation")
|
||||||
private IrisAxisRotationClamp zAxis = new IrisAxisRotationClamp();
|
private IrisAxisRotationClamp zAxis = new IrisAxisRotationClamp();
|
||||||
|
|
||||||
|
private final transient ConcurrentHashMap<NativeBlockState, Object[]> stateRotations = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public static IrisObjectRotation xFlip180() {
|
public static IrisObjectRotation xFlip180() {
|
||||||
IrisObjectRotation rt = new IrisObjectRotation();
|
IrisObjectRotation rt = new IrisObjectRotation();
|
||||||
IrisAxisRotationClamp rtx = new IrisAxisRotationClamp();
|
IrisAxisRotationClamp rtx = new IrisAxisRotationClamp();
|
||||||
@@ -316,9 +321,19 @@ public class IrisObjectRotation {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockData raw = original.clone();
|
Object[] rotations = stateRotations.computeIfAbsent(state, (NativeBlockState ignored) -> new Object[SPIN_STEPS * SPIN_STEPS * SPIN_STEPS]);
|
||||||
BlockData rotated = rotate(raw, spinx, spiny, spinz);
|
int index = (spinStep(spinx) * SPIN_STEPS + spinStep(spiny)) * SPIN_STEPS + spinStep(spinz);
|
||||||
return rotated == null ? null : BukkitBlockState.of(rotated);
|
Object memo = rotations[index];
|
||||||
|
if (memo == null) {
|
||||||
|
BlockData rotated = rotate(original.clone(), spinx, spiny, spinz);
|
||||||
|
memo = rotated == null ? ROTATED_AWAY : BukkitBlockState.of(rotated);
|
||||||
|
rotations[index] = memo;
|
||||||
|
}
|
||||||
|
return memo == ROTATED_AWAY ? null : (NativeBlockState) memo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int spinStep(int spin) {
|
||||||
|
return (int) Math.ceil(Math.abs((spin % 360D) / 90D));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean canRotateBlockData(BlockData data) {
|
private static boolean canRotateBlockData(BlockData data) {
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import static org.junit.Assert.assertTrue;
|
|||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.mockingDetails;
|
||||||
|
|
||||||
public class BukkitSpiConformanceTest {
|
public class BukkitSpiConformanceTest {
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
@@ -181,6 +182,23 @@ public class BukkitSpiConformanceTest {
|
|||||||
assertEquals("iristest:append_block[axis=y,lit=true]", merged.key());
|
assertEquals("iristest:append_block[axis=y,lit=true]", merged.key());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void withPropertyParsesEachVariantOnce() {
|
||||||
|
String merged = "iristest:memo_leaves[distance=1,persistent=true]";
|
||||||
|
NativeBlockState state = BukkitBlockState.of(blockData("iristest:memo_leaves[distance=1,persistent=false]"));
|
||||||
|
|
||||||
|
NativeBlockState first = state.withProperty("persistent", "true");
|
||||||
|
NativeBlockState second = state.withProperty("persistent", "true");
|
||||||
|
|
||||||
|
assertSame(first, second);
|
||||||
|
assertEquals(merged, first.key());
|
||||||
|
long parses = mockingDetails(Bukkit.getServer()).getInvocations().stream()
|
||||||
|
.filter(invocation -> invocation.getMethod().getName().equals("createBlockData")
|
||||||
|
&& merged.equals(invocation.getArgument(0)))
|
||||||
|
.count();
|
||||||
|
assertEquals(1L, parses);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void withPropertyAddsBracketSectionWhenAbsent() {
|
public void withPropertyAddsBracketSectionWhenAbsent() {
|
||||||
BlockData data = blockData("iristest:bare_block");
|
BlockData data = blockData("iristest:bare_block");
|
||||||
|
|||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
package art.arcane.iris.structure.object;
|
||||||
|
|
||||||
|
import art.arcane.iris.platform.bukkit.BukkitBlockState;
|
||||||
|
import art.arcane.iris.testsupport.BukkitTestServer;
|
||||||
|
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||||
|
import org.bukkit.Axis;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.block.data.Orientable;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.withSettings;
|
||||||
|
|
||||||
|
public class IrisObjectRotationStateMemoTest {
|
||||||
|
@BeforeClass
|
||||||
|
public static void installServer() {
|
||||||
|
BukkitTestServer.install();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rotatedStatesAreComputedOncePerQuantizedSpin() {
|
||||||
|
AtomicInteger clones = new AtomicInteger();
|
||||||
|
BlockData original = log("iristest:memo_log[axis=x]", Axis.X);
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
clones.incrementAndGet();
|
||||||
|
return log("iristest:memo_log[axis=x]", Axis.X);
|
||||||
|
}).when(original).clone();
|
||||||
|
NativeBlockState state = BukkitBlockState.of(original);
|
||||||
|
IrisObjectRotation rotation = IrisObjectRotation.of(0, 90, 0);
|
||||||
|
|
||||||
|
NativeBlockState first = rotation.rotate(state, 0, 0, 0);
|
||||||
|
NativeBlockState repeated = rotation.rotate(state, 0, 0, 0);
|
||||||
|
rotation.rotate(state, 0, 30, 0);
|
||||||
|
rotation.rotate(state, 0, 90, 0);
|
||||||
|
|
||||||
|
assertSame(first, repeated);
|
||||||
|
assertEquals(2, clones.get());
|
||||||
|
assertEquals("iristest:memo_log[axis=x]", first.key());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BlockData log(String key, Axis axis) {
|
||||||
|
BlockData data = mock(BlockData.class, withSettings().extraInterfaces(Orientable.class));
|
||||||
|
doReturn(key).when(data).getAsString();
|
||||||
|
doReturn(key).when(data).getAsString(anyBoolean());
|
||||||
|
doReturn(axis).when((Orientable) data).getAxis();
|
||||||
|
doReturn(Set.of(Axis.X, Axis.Y, Axis.Z)).when((Orientable) data).getAxes();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user