mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +00:00
Palette section buffer
- Add SectionBuffer: chunk palette of written instances and short[4096] ids per section in y, z, x order - Column tops are indexed per x-slice and padded so fan-out slices do not share a cache line - Add ColumnRunHunk and forward column runs through the listening hunk and block update listener - Terrain painter emits column runs and evaluates ores only inside the column's ore bands - Bukkit generation writes into SectionBuffer and applies it through ChunkDataOutput - Remove ChunkDataHunkHolder and the probe's ProductionBlockHunk copy - Probe production buffers use SectionBuffer
This commit is contained in:
1 parent
f283d482a4
commit
4d2e52bcc7
19 files changed
+955
-469
No files matched your search
@@ -6,8 +6,7 @@ import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.iris.spi.PlatformRegistries;
|
||||
import art.arcane.iris.testsupport.PlatformLeakGuard;
|
||||
import art.arcane.iris.generation.chunk.ChunkDataHunkHolder;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import art.arcane.iris.generation.chunk.SectionBuffer;
|
||||
import org.junit.After;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
@@ -37,7 +36,7 @@ public class PlatformStateRebindTest {
|
||||
"art.arcane.iris.generation.biome.IrisBiomeLayerGenerator",
|
||||
"art.arcane.iris.studio.generation.BiomeBuffetGenerator",
|
||||
"art.arcane.iris.generation.mantle.EngineMantle",
|
||||
"art.arcane.iris.generation.chunk.ChunkDataHunkHolder");
|
||||
"art.arcane.iris.generation.chunk.SectionBuffer");
|
||||
|
||||
@After
|
||||
public void unbindPlatform() {
|
||||
@@ -47,10 +46,7 @@ public class PlatformStateRebindTest {
|
||||
@Test
|
||||
public void chunkDataAirIsResolvedAgainstTheCurrentlyBoundPlatform() {
|
||||
NativeBlockState first = bindPlatform("first");
|
||||
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
|
||||
when(chunk.getMinHeight()).thenReturn(-64);
|
||||
when(chunk.getMaxHeight()).thenReturn(320);
|
||||
ChunkDataHunkHolder holder = new ChunkDataHunkHolder(chunk);
|
||||
SectionBuffer holder = new SectionBuffer(384);
|
||||
|
||||
assertSame(first, holder.getRaw(0, -1, 0));
|
||||
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
package art.arcane.iris.generation.chunk;
|
||||
|
||||
import art.arcane.iris.platform.bukkit.nms.INMS;
|
||||
import art.arcane.iris.platform.bukkit.nms.INMSBinding;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ChunkDataHunkHolderTest {
|
||||
@Test
|
||||
public void columnTopTracksTheHighestStoredValueAndIgnoresNulls() {
|
||||
ChunkDataHunkHolder holder = holder(64);
|
||||
NativeBlockState stone = mock(NativeBlockState.class);
|
||||
|
||||
assertEquals(-1, holder.highestStoredY(3, 7));
|
||||
holder.setRaw(3, 40, 7, null);
|
||||
assertEquals(-1, holder.highestStoredY(3, 7));
|
||||
holder.setRaw(3, 12, 7, stone);
|
||||
holder.setRaw(3, 30, 7, stone);
|
||||
holder.setRaw(3, 5, 7, stone);
|
||||
|
||||
assertEquals(30, holder.highestStoredY(3, 7));
|
||||
assertEquals(-1, holder.highestStoredY(7, 3));
|
||||
assertSame(stone, holder.getStoredRaw(3, 30, 7));
|
||||
assertNull(holder.getStoredRaw(3, 31, 7));
|
||||
assertEquals(30, ColumnExtent.highestStoredY(new ColumnExtentListeningHunk<>(holder, (x, y, z, state) -> {
|
||||
}), 3, 7));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cellsKeepTheZMajorLayout() {
|
||||
ChunkDataHunkHolder holder = holder(32);
|
||||
List<NativeBlockState> states = new ArrayList<>();
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int y = 0; y < 32; y++) {
|
||||
NativeBlockState state = mock(NativeBlockState.class);
|
||||
states.add(state);
|
||||
holder.setRaw(x, y, z, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
int index = 0;
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int y = 0; y < 32; y++) {
|
||||
assertSame(states.get(index++), holder.getStoredRaw(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void concurrentWritersNeverLoseTheColumnMaximum() throws InterruptedException {
|
||||
for (int trial = 0; trial < 20; trial++) {
|
||||
ChunkDataHunkHolder holder = holder(768);
|
||||
NativeBlockState stone = mock(NativeBlockState.class);
|
||||
int threads = 8;
|
||||
CountDownLatch start = new CountDownLatch(1);
|
||||
List<Thread> writers = new ArrayList<>();
|
||||
for (int thread = 0; thread < threads; thread++) {
|
||||
int offset = thread;
|
||||
Thread writer = new Thread(() -> {
|
||||
try {
|
||||
start.await();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
for (int y = offset; y < 768; y += threads) {
|
||||
holder.setRaw(9, y, 4, stone);
|
||||
}
|
||||
});
|
||||
writer.start();
|
||||
writers.add(writer);
|
||||
}
|
||||
start.countDown();
|
||||
for (Thread writer : writers) {
|
||||
writer.join();
|
||||
}
|
||||
assertEquals(767, holder.highestStoredY(9, 4));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fallbackAppliesExplicitCaveAirBetweenSolidRunsWithNegativeMinimumHeight() {
|
||||
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
|
||||
when(chunk.getMinHeight()).thenReturn(-256);
|
||||
when(chunk.getMaxHeight()).thenReturn(512);
|
||||
ChunkDataHunkHolder holder = new ChunkDataHunkHolder(chunk);
|
||||
BlockData dirt = mock(BlockData.class);
|
||||
BlockData coal = mock(BlockData.class);
|
||||
BlockData caveAir = mock(BlockData.class);
|
||||
NativeBlockState dirtState = state(dirt);
|
||||
NativeBlockState coalState = state(coal);
|
||||
NativeBlockState caveAirState = state(caveAir);
|
||||
BlockData[] applied = new BlockData[768];
|
||||
Arrays.fill(applied, dirt);
|
||||
for (int y = 0; y <= 75; y++) {
|
||||
holder.setRaw(14, y + 256, 6, y < 33 ? dirtState : y <= 54 ? caveAirState : coalState);
|
||||
}
|
||||
doAnswer(call -> {
|
||||
int y = call.getArgument(1);
|
||||
applied[y + 256] = call.getArgument(3);
|
||||
return null;
|
||||
}).when(chunk).setBlock(anyInt(), anyInt(), anyInt(), any(BlockData.class));
|
||||
doAnswer(call -> {
|
||||
int from = call.getArgument(1);
|
||||
int to = call.getArgument(4);
|
||||
BlockData block = call.getArgument(6);
|
||||
Arrays.fill(applied, from + 256, to + 256, block);
|
||||
return null;
|
||||
}).when(chunk).setRegion(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(BlockData.class));
|
||||
|
||||
try (MockedStatic<INMS> nms = mockStatic(INMS.class)) {
|
||||
nms.when(INMS::get).thenReturn(mock(INMSBinding.class));
|
||||
holder.apply();
|
||||
}
|
||||
|
||||
for (int y = -256; y < 512; y++) {
|
||||
BlockData expected = y >= 33 && y <= 54 ? caveAir : y >= 55 && y <= 75 ? coal : dirt;
|
||||
assertSame("column at " + y, expected, applied[y + 256]);
|
||||
}
|
||||
}
|
||||
|
||||
private static NativeBlockState state(BlockData data) {
|
||||
NativeBlockState state = mock(NativeBlockState.class);
|
||||
when(state.nativeHandle()).thenReturn(data);
|
||||
return state;
|
||||
}
|
||||
|
||||
private static ChunkDataHunkHolder holder(int height) {
|
||||
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
|
||||
when(chunk.getMinHeight()).thenReturn(0);
|
||||
when(chunk.getMaxHeight()).thenReturn(height);
|
||||
return new ChunkDataHunkHolder(chunk);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package art.arcane.iris.generation.chunk;
|
||||
|
||||
import art.arcane.iris.platform.bukkit.nms.INMS;
|
||||
import art.arcane.iris.platform.bukkit.nms.INMSBinding;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ChunkDataOutputTest {
|
||||
@Test
|
||||
public void nativeWriterReceivesTheSectionBuffer() {
|
||||
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
|
||||
SectionBuffer blocks = new SectionBuffer(64);
|
||||
INMSBinding binding = mock(INMSBinding.class);
|
||||
when(binding.applyChunkDataBlocks(chunk, blocks)).thenReturn(true);
|
||||
|
||||
try (MockedStatic<INMS> nms = mockStatic(INMS.class)) {
|
||||
nms.when(INMS::get).thenReturn(binding);
|
||||
ChunkDataOutput.apply(chunk, blocks);
|
||||
}
|
||||
|
||||
verify(binding).applyChunkDataBlocks(chunk, blocks);
|
||||
verify(chunk, never()).setRegion(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(BlockData.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fallbackAppliesExplicitCaveAirBetweenSolidRunsWithNegativeMinimumHeight() {
|
||||
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
|
||||
when(chunk.getMinHeight()).thenReturn(-256);
|
||||
when(chunk.getMaxHeight()).thenReturn(512);
|
||||
SectionBuffer blocks = new SectionBuffer(768);
|
||||
BlockData dirt = mock(BlockData.class);
|
||||
BlockData coal = mock(BlockData.class);
|
||||
BlockData caveAir = mock(BlockData.class);
|
||||
NativeBlockState dirtState = state(dirt);
|
||||
NativeBlockState coalState = state(coal);
|
||||
NativeBlockState caveAirState = state(caveAir);
|
||||
BlockData[] applied = new BlockData[768];
|
||||
Arrays.fill(applied, dirt);
|
||||
for (int y = 0; y <= 75; y++) {
|
||||
blocks.setRaw(14, y + 256, 6, y < 33 ? dirtState : y <= 54 ? caveAirState : coalState);
|
||||
}
|
||||
doAnswer(call -> {
|
||||
int y = call.getArgument(1);
|
||||
applied[y + 256] = call.getArgument(3);
|
||||
return null;
|
||||
}).when(chunk).setBlock(anyInt(), anyInt(), anyInt(), any(BlockData.class));
|
||||
doAnswer(call -> {
|
||||
int from = call.getArgument(1);
|
||||
int to = call.getArgument(4);
|
||||
BlockData block = call.getArgument(6);
|
||||
Arrays.fill(applied, from + 256, to + 256, block);
|
||||
return null;
|
||||
}).when(chunk).setRegion(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(BlockData.class));
|
||||
|
||||
try (MockedStatic<INMS> nms = mockStatic(INMS.class)) {
|
||||
nms.when(INMS::get).thenReturn(mock(INMSBinding.class));
|
||||
ChunkDataOutput.apply(chunk, blocks);
|
||||
}
|
||||
|
||||
for (int y = -256; y < 512; y++) {
|
||||
BlockData expected = y >= 33 && y <= 54 ? caveAir : y >= 55 && y <= 75 ? coal : dirt;
|
||||
assertSame("column at " + y, expected, applied[y + 256]);
|
||||
}
|
||||
assertTrue(blocks.highestStoredY(14, 6) == 331);
|
||||
}
|
||||
|
||||
private static NativeBlockState state(BlockData data) {
|
||||
NativeBlockState state = mock(NativeBlockState.class);
|
||||
when(state.nativeHandle()).thenReturn(data);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
package art.arcane.iris.generation.chunk;
|
||||
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.PlatformRegistries;
|
||||
import art.arcane.iris.testsupport.PlatformLeakGuard;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.volmlib.util.hunk.Hunk;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class SectionBufferTest {
|
||||
@ClassRule
|
||||
public static final PlatformLeakGuard PLATFORM_GUARD = PlatformLeakGuard.clean();
|
||||
|
||||
private NativeBlockState air;
|
||||
|
||||
@Before
|
||||
public void bindPlatform() {
|
||||
air = mock(NativeBlockState.class);
|
||||
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||
when(registries.block(anyString())).thenReturn(air);
|
||||
IrisPlatform platform = mock(IrisPlatform.class);
|
||||
when(platform.registries()).thenReturn(registries);
|
||||
IrisPlatforms.bind(platform);
|
||||
}
|
||||
|
||||
@After
|
||||
public void unbindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void columnTopTracksTheHighestStoredValueAndIgnoresNulls() {
|
||||
SectionBuffer buffer = new SectionBuffer(64);
|
||||
NativeBlockState stone = mock(NativeBlockState.class);
|
||||
|
||||
assertEquals(-1, buffer.highestStoredY(3, 7));
|
||||
buffer.setRaw(3, 40, 7, null);
|
||||
assertEquals(-1, buffer.highestStoredY(3, 7));
|
||||
buffer.setRaw(3, 12, 7, stone);
|
||||
buffer.setRaw(3, 30, 7, stone);
|
||||
buffer.setRaw(3, 5, 7, stone);
|
||||
|
||||
assertEquals(30, buffer.highestStoredY(3, 7));
|
||||
assertEquals(-1, buffer.highestStoredY(7, 3));
|
||||
assertSame(stone, buffer.getStoredRaw(3, 30, 7));
|
||||
assertNull(buffer.getStoredRaw(3, 31, 7));
|
||||
assertEquals(30, ColumnExtent.highestStoredY(new ColumnExtentListeningHunk<>(buffer, (x, y, z, state) -> {
|
||||
}), 3, 7));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unwrittenAndOutOfRangeCellsReadAsAir() {
|
||||
SectionBuffer buffer = new SectionBuffer(48);
|
||||
NativeBlockState stone = mock(NativeBlockState.class);
|
||||
buffer.setRaw(2, 20, 9, stone);
|
||||
|
||||
assertSame(air, buffer.getRaw(2, 21, 9));
|
||||
assertSame(air, buffer.getRaw(2, 3, 9));
|
||||
assertSame(air, buffer.getRaw(2, -1, 9));
|
||||
assertSame(air, buffer.getRaw(2, 48, 9));
|
||||
assertSame(stone, buffer.getRaw(2, 20, 9));
|
||||
assertNull(buffer.getStoredRaw(2, 21, 9));
|
||||
assertNull(buffer.getStoredRaw(2, 3, 9));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void paletteKeepsEveryDistinctInstance() {
|
||||
SectionBuffer buffer = new SectionBuffer(32);
|
||||
NativeBlockState first = mock(NativeBlockState.class);
|
||||
NativeBlockState second = mock(NativeBlockState.class);
|
||||
when(first.matches(second)).thenReturn(true);
|
||||
when(second.matches(first)).thenReturn(true);
|
||||
|
||||
buffer.setRaw(0, 0, 0, first);
|
||||
buffer.setRaw(1, 0, 0, second);
|
||||
buffer.setRaw(2, 0, 0, first);
|
||||
|
||||
assertSame(first, buffer.getRaw(0, 0, 0));
|
||||
assertSame(second, buffer.getRaw(1, 0, 0));
|
||||
assertSame(first, buffer.getRaw(2, 0, 0));
|
||||
assertEquals(buffer.paletteId(first), buffer.paletteId(first));
|
||||
assertNotEquals(buffer.paletteId(first), buffer.paletteId(second));
|
||||
assertEquals(0, buffer.paletteId(null));
|
||||
NativeBlockState[] palette = buffer.palette();
|
||||
assertEquals(3, palette.length);
|
||||
assertNull(palette[0]);
|
||||
assertSame(first, palette[buffer.paletteId(first)]);
|
||||
assertSame(second, palette[buffer.paletteId(second)]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sectionsUseTheYzxLayoutAndStayUnallocatedUntilWritten() {
|
||||
SectionBuffer buffer = new SectionBuffer(64);
|
||||
NativeBlockState stone = mock(NativeBlockState.class);
|
||||
NativeBlockState dirt = mock(NativeBlockState.class);
|
||||
buffer.setRaw(5, 37, 11, stone);
|
||||
buffer.setRaw(15, 47, 15, dirt);
|
||||
|
||||
assertEquals(4, buffer.sectionCount());
|
||||
assertNull(buffer.section(0));
|
||||
assertNull(buffer.section(1));
|
||||
assertNull(buffer.section(3));
|
||||
short[] section = buffer.section(2);
|
||||
assertEquals(4096, section.length);
|
||||
assertEquals(buffer.paletteId(stone), section[(5 << 8) | (11 << 4) | 5]);
|
||||
assertEquals(buffer.paletteId(dirt), section[(15 << 8) | (15 << 4) | 15]);
|
||||
int written = 0;
|
||||
for (short id : section) {
|
||||
written += id == 0 ? 0 : 1;
|
||||
}
|
||||
assertEquals(2, written);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void columnRunsMatchRepeatedSingleWrites() {
|
||||
Random random = new Random(41L);
|
||||
List<NativeBlockState> pool = new ArrayList<>();
|
||||
for (int index = 0; index < 9; index++) {
|
||||
pool.add(mock(NativeBlockState.class));
|
||||
}
|
||||
pool.add(null);
|
||||
for (int trial = 0; trial < 40; trial++) {
|
||||
int height = 16 * (1 + random.nextInt(12));
|
||||
SectionBuffer runs = new SectionBuffer(height);
|
||||
SectionBuffer cells = new SectionBuffer(height);
|
||||
for (int write = 0; write < 300; write++) {
|
||||
int x = random.nextInt(16);
|
||||
int z = random.nextInt(16);
|
||||
int from = random.nextInt(height);
|
||||
int to = Math.min(height - 1, from + random.nextInt(70));
|
||||
NativeBlockState state = pool.get(random.nextInt(pool.size()));
|
||||
switch (random.nextInt(3)) {
|
||||
case 0 -> runs.fillColumnRun(x, z, from, to, state);
|
||||
case 1 -> runs.fillColumnRun(x, z, from, to, runs.paletteId(state));
|
||||
default -> ColumnRunHunk.fillColumnRun(runs, x, z, from, to, state);
|
||||
}
|
||||
for (int y = to; y >= from; y--) {
|
||||
cells.setRaw(x, y, z, state);
|
||||
}
|
||||
}
|
||||
assertSameContents(cells, runs);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runsWithAnEmptyRangeOrNoStateWriteNothing() {
|
||||
SectionBuffer buffer = new SectionBuffer(32);
|
||||
NativeBlockState stone = mock(NativeBlockState.class);
|
||||
buffer.fillColumnRun(1, 1, 10, 9, stone);
|
||||
buffer.fillColumnRun(1, 1, 3, 9, (NativeBlockState) null);
|
||||
buffer.fillColumnRun(1, 1, 3, 9, 0);
|
||||
|
||||
assertEquals(-1, buffer.highestStoredY(1, 1));
|
||||
assertNull(buffer.section(0));
|
||||
assertEquals(1, buffer.palette().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void concurrentWritersNeverLoseTheColumnMaximum() throws InterruptedException {
|
||||
for (int trial = 0; trial < 20; trial++) {
|
||||
SectionBuffer buffer = new SectionBuffer(768);
|
||||
NativeBlockState stone = mock(NativeBlockState.class);
|
||||
int threads = 8;
|
||||
runConcurrently(threads, thread -> {
|
||||
for (int y = thread; y < 768; y += threads) {
|
||||
buffer.setRaw(9, y, 4, stone);
|
||||
}
|
||||
});
|
||||
assertEquals(767, buffer.highestStoredY(9, 4));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void concurrentPaletteGrowthKeepsOneIdPerInstance() throws InterruptedException {
|
||||
int threads = 8;
|
||||
int statesPerThread = 300;
|
||||
NativeBlockState[][] states = new NativeBlockState[threads][statesPerThread];
|
||||
NativeBlockState[] shared = new NativeBlockState[40];
|
||||
for (int index = 0; index < shared.length; index++) {
|
||||
shared[index] = mock(NativeBlockState.class);
|
||||
}
|
||||
for (int thread = 0; thread < threads; thread++) {
|
||||
for (int index = 0; index < statesPerThread; index++) {
|
||||
states[thread][index] = mock(NativeBlockState.class);
|
||||
}
|
||||
}
|
||||
for (int trial = 0; trial < 10; trial++) {
|
||||
SectionBuffer buffer = new SectionBuffer(384);
|
||||
runConcurrently(threads, thread -> {
|
||||
for (int index = 0; index < statesPerThread; index++) {
|
||||
int x = thread * 2 + (index & 1);
|
||||
int z = (index >> 1) & 15;
|
||||
int y = index;
|
||||
buffer.setRaw(x, y, z, states[thread][index]);
|
||||
buffer.setRaw(x, y + 1, z, shared[index % shared.length]);
|
||||
}
|
||||
});
|
||||
for (int thread = 0; thread < threads; thread++) {
|
||||
for (int index = 0; index < statesPerThread; index++) {
|
||||
int x = thread * 2 + (index & 1);
|
||||
int z = (index >> 1) & 15;
|
||||
assertSame(states[thread][index], buffer.getStoredRaw(x, index, z));
|
||||
assertSame(shared[index % shared.length], buffer.getRaw(x, index + 1, z));
|
||||
}
|
||||
}
|
||||
assertEquals(1 + threads * statesPerThread + shared.length, buffer.palette().length);
|
||||
for (NativeBlockState state : shared) {
|
||||
assertSame(state, buffer.palette()[buffer.paletteId(state)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listeningHunksForwardRunsInTopDownOrder() {
|
||||
SectionBuffer buffer = new SectionBuffer(32);
|
||||
NativeBlockState stone = mock(NativeBlockState.class);
|
||||
List<Integer> heard = new ArrayList<>();
|
||||
ColumnExtentListeningHunk<NativeBlockState> listening = new ColumnExtentListeningHunk<>(buffer,
|
||||
(x, y, z, state) -> heard.add(y));
|
||||
|
||||
ColumnRunHunk.fillColumnRun(listening, 4, 6, 10, 13, stone);
|
||||
|
||||
assertEquals(List.of(13, 12, 11, 10), heard);
|
||||
for (int y = 10; y <= 13; y++) {
|
||||
assertSame(stone, buffer.getStoredRaw(4, y, 6));
|
||||
}
|
||||
assertNull(buffer.getStoredRaw(4, 9, 6));
|
||||
assertEquals(13, buffer.highestStoredY(4, 6));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void plainHunksReceiveRunsAsTopDownSingleWrites() {
|
||||
Hunk<NativeBlockState> array = Hunk.newArrayHunk(16, 32, 16);
|
||||
NativeBlockState stone = mock(NativeBlockState.class);
|
||||
array.setRaw(2, 5, 2, stone);
|
||||
List<Integer> order = new ArrayList<>();
|
||||
Hunk<NativeBlockState> recording = new ColumnExtentListeningHunk<>(array, (x, y, z, state) -> order.add(y));
|
||||
|
||||
ColumnRunHunk.fillColumnRun(Hunk.newArrayHunk(16, 32, 16), 0, 0, 0, 3, stone);
|
||||
ColumnRunHunk.fillColumnRun(array, 2, 2, 4, 6, null);
|
||||
ColumnRunHunk.fillColumnRun(recording, 1, 1, 7, 9, stone);
|
||||
|
||||
assertNull(array.getRaw(2, 5, 2));
|
||||
assertSame(stone, array.getRaw(1, 8, 1));
|
||||
assertEquals(List.of(9, 8, 7), order);
|
||||
}
|
||||
|
||||
private static void assertSameContents(SectionBuffer expected, SectionBuffer actual) {
|
||||
assertEquals(expected.sectionCount(), actual.sectionCount());
|
||||
NativeBlockState[] expectedPalette = expected.palette();
|
||||
NativeBlockState[] actualPalette = actual.palette();
|
||||
for (int sectionIndex = 0; sectionIndex < expected.sectionCount(); sectionIndex++) {
|
||||
short[] expectedSection = expected.section(sectionIndex);
|
||||
short[] actualSection = actual.section(sectionIndex);
|
||||
if (expectedSection == null) {
|
||||
assertNull(actualSection);
|
||||
continue;
|
||||
}
|
||||
NativeBlockState[] expectedStates = new NativeBlockState[4096];
|
||||
NativeBlockState[] actualStates = new NativeBlockState[4096];
|
||||
for (int index = 0; index < 4096; index++) {
|
||||
expectedStates[index] = expectedPalette[expectedSection[index] & 0xFFFF];
|
||||
actualStates[index] = actualPalette[actualSection[index] & 0xFFFF];
|
||||
}
|
||||
assertArrayEquals(expectedStates, actualStates);
|
||||
}
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
assertEquals(expected.highestStoredY(x, z), actual.highestStoredY(x, z));
|
||||
}
|
||||
}
|
||||
assertEquals(Arrays.stream(expectedPalette).filter(state -> state != null).distinct().count(),
|
||||
Arrays.stream(actualPalette).filter(state -> state != null).distinct().count());
|
||||
}
|
||||
|
||||
private static void runConcurrently(int threads, ThreadTask task) throws InterruptedException {
|
||||
CountDownLatch start = new CountDownLatch(1);
|
||||
List<Thread> workers = new ArrayList<>();
|
||||
for (int thread = 0; thread < threads; thread++) {
|
||||
int index = thread;
|
||||
Thread worker = new Thread(() -> {
|
||||
try {
|
||||
start.await();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
task.run(index);
|
||||
});
|
||||
worker.start();
|
||||
workers.add(worker);
|
||||
}
|
||||
start.countDown();
|
||||
for (Thread worker : workers) {
|
||||
worker.join();
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ThreadTask {
|
||||
void run(int thread);
|
||||
}
|
||||
}
|
||||
@@ -19,11 +19,10 @@ import art.arcane.iris.generation.decoration.IrisDecorator;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.volmlib.util.hunk.Hunk;
|
||||
import art.arcane.iris.generation.block.B;
|
||||
import art.arcane.iris.generation.chunk.ChunkDataHunkHolder;
|
||||
import art.arcane.iris.generation.chunk.SectionBuffer;
|
||||
import art.arcane.iris.generation.chunk.ColumnExtentListeningHunk;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
@@ -165,10 +164,7 @@ public class IrisNaturalCaveScanTest {
|
||||
fullOutput.listen((x, y, z, state) -> fullWrites.add(x + ":" + y + ":" + z + ":" + state.key())), null);
|
||||
|
||||
Fixture bounded = new Fixture(true);
|
||||
ChunkGenerator.ChunkData chunkData = mock(ChunkGenerator.ChunkData.class);
|
||||
doReturn(0).when(chunkData).getMinHeight();
|
||||
doReturn(32).when(chunkData).getMaxHeight();
|
||||
ChunkDataHunkHolder holder = new ChunkDataHunkHolder(chunkData);
|
||||
SectionBuffer holder = new SectionBuffer(32);
|
||||
bounded.sparseOutput(holder);
|
||||
List<String> boundedWrites = new ArrayList<>();
|
||||
bounded.modifier.decorateNaturalCaves(-32, 48, new ColumnExtentListeningHunk<>(holder,
|
||||
|
||||
+3
-11
@@ -1,7 +1,7 @@
|
||||
package art.arcane.iris.generation.stage;
|
||||
|
||||
import art.arcane.iris.generation.block.B;
|
||||
import art.arcane.iris.generation.chunk.ChunkDataHunkHolder;
|
||||
import art.arcane.iris.generation.chunk.SectionBuffer;
|
||||
import art.arcane.iris.generation.chunk.ColumnExtentListeningHunk;
|
||||
import art.arcane.iris.generation.concurrent.BurstExecutor;
|
||||
import art.arcane.iris.generation.concurrent.MultiBurst;
|
||||
@@ -15,7 +15,6 @@ import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.volmlib.util.hunk.Hunk;
|
||||
import art.arcane.volmlib.util.hunk.storage.ArrayHunk;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -65,8 +64,8 @@ public class IrisPerfectionModifierColumnTest {
|
||||
NativeBlockState[] palette = {null, fixture.air, fixture.water, fixture.stone,
|
||||
fixture.decorant, fixture.half, fixture.spike(true, false), fixture.spike(false, true)};
|
||||
for (int trial = 0; trial < 8; trial++) {
|
||||
ChunkDataHunkHolder bounded = holder(64);
|
||||
ChunkDataHunkHolder full = holder(64);
|
||||
SectionBuffer bounded = new SectionBuffer(64);
|
||||
SectionBuffer full = new SectionBuffer(64);
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
int top = random.nextInt(64);
|
||||
@@ -91,13 +90,6 @@ public class IrisPerfectionModifierColumnTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static ChunkDataHunkHolder holder(int height) {
|
||||
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
|
||||
when(chunk.getMinHeight()).thenReturn(0);
|
||||
when(chunk.getMaxHeight()).thenReturn(height);
|
||||
return new ChunkDataHunkHolder(chunk);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unrelatedTallColumnsAreScannedOnceDuringRepeatedCleanup() {
|
||||
Fixture fixture = new Fixture();
|
||||
|
||||
Reference in new issue
Block a user