mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 13:30:08 +00:00
Simplified object smart boring
- Smart boring runs direct axis passes under the existing write lock - Negative object bounds stop at the last occupied coordinate - Bore insertion avoids redundant map callbacks - Regression tests cover ordered fills and repeated calls
This commit is contained in:
1 parent
606aa2e704
commit
1a2c8a8845
2 files changed
+192
-96
No files matched your search
@@ -23,13 +23,9 @@ import art.arcane.iris.spi.PlatformBlockState;
|
|||||||
import art.arcane.iris.util.common.data.VectorMap;
|
import art.arcane.iris.util.common.data.VectorMap;
|
||||||
import art.arcane.iris.util.common.math.IrisBlockVector;
|
import art.arcane.iris.util.common.math.IrisBlockVector;
|
||||||
import art.arcane.iris.util.common.math.Vector3i;
|
import art.arcane.iris.util.common.math.Vector3i;
|
||||||
import art.arcane.iris.util.common.parallel.BurstExecutor;
|
|
||||||
import art.arcane.iris.util.common.parallel.MultiBurst;
|
|
||||||
import art.arcane.volmlib.util.format.Form;
|
import art.arcane.volmlib.util.format.Form;
|
||||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Volume shaping for {@link IrisObject}: smart boring, shrinkwrapping, compaction and the shared block
|
* Volume shaping for {@link IrisObject}: smart boring, shrinkwrapping, compaction and the shared block
|
||||||
* classification helpers used by placement.
|
* classification helpers used by placement.
|
||||||
@@ -59,30 +55,23 @@ final class IrisObjectShaping {
|
|||||||
private static void ensureSmartBoredLocked(IrisObject self) {
|
private static void ensureSmartBoredLocked(IrisObject self) {
|
||||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||||
PlatformBlockState vair = IrisObject.States.VAIR;
|
PlatformBlockState vair = IrisObject.States.VAIR;
|
||||||
AtomicInteger applied = new AtomicInteger();
|
int applied = 0;
|
||||||
IrisBlockVector max = new IrisBlockVector(Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE);
|
IrisBlockVector max = new IrisBlockVector(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE);
|
||||||
IrisBlockVector min = new IrisBlockVector(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE);
|
IrisBlockVector min = new IrisBlockVector(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||||
VectorMap<PlatformBlockState> source;
|
VectorMap<PlatformBlockState> source = self.blocks;
|
||||||
self.readLock.lock();
|
if (source.isEmpty()) {
|
||||||
try {
|
IrisLogging.warn("Cannot Smart Bore " + self.getLoadKey() + " because it has 0 blocks in it.");
|
||||||
if (self.blocks.isEmpty()) {
|
self.smartBored = true;
|
||||||
IrisLogging.warn("Cannot Smart Bore " + self.getLoadKey() + " because it has 0 blocks in it.");
|
return;
|
||||||
self.smartBored = true;
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
source = self.blocks;
|
for (IrisBlockVector i : source.keys()) {
|
||||||
|
max.setX(Math.max(i.getX(), max.getX()));
|
||||||
for (IrisBlockVector i : self.blocks.keys()) {
|
min.setX(Math.min(i.getX(), min.getX()));
|
||||||
max.setX(Math.max(i.getX(), max.getX()));
|
max.setY(Math.max(i.getY(), max.getY()));
|
||||||
min.setX(Math.min(i.getX(), min.getX()));
|
min.setY(Math.min(i.getY(), min.getY()));
|
||||||
max.setY(Math.max(i.getY(), max.getY()));
|
max.setZ(Math.max(i.getZ(), max.getZ()));
|
||||||
min.setY(Math.min(i.getY(), min.getY()));
|
min.setZ(Math.min(i.getZ(), min.getZ()));
|
||||||
max.setZ(Math.max(i.getZ(), max.getZ()));
|
|
||||||
min.setZ(Math.min(i.getZ(), min.getZ()));
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
self.readLock.unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VectorMap<PlatformBlockState> bore = new VectorMap<>();
|
VectorMap<PlatformBlockState> bore = new VectorMap<>();
|
||||||
@@ -90,114 +79,94 @@ final class IrisObjectShaping {
|
|||||||
// bored volume must come from a fixed X->Y->Z order on the calling (lock-owning)
|
// bored volume must come from a fixed X->Y->Z order on the calling (lock-owning)
|
||||||
// thread, not from pool scheduling. This matches what burst-worker callers (studio,
|
// thread, not from pool scheduling. This matches what burst-worker callers (studio,
|
||||||
// generation threads) already produced, so output is unchanged where it was stable.
|
// generation threads) already produced, so output is unchanged where it was stable.
|
||||||
BurstExecutor burst = MultiBurst.burst.burst();
|
|
||||||
burst.setMulticore(false);
|
|
||||||
|
|
||||||
// Smash X
|
// Smash X
|
||||||
for (int rayY = min.getBlockY(); rayY <= max.getBlockY(); rayY++) {
|
for (int rayY = min.getBlockY(); rayY <= max.getBlockY(); rayY++) {
|
||||||
int finalRayY = rayY;
|
for (int rayZ = min.getBlockZ(); rayZ <= max.getBlockZ(); rayZ++) {
|
||||||
burst.queue(() -> {
|
int start = Integer.MAX_VALUE;
|
||||||
for (int rayZ = min.getBlockZ(); rayZ <= max.getBlockZ(); rayZ++) {
|
int end = Integer.MIN_VALUE;
|
||||||
int start = Integer.MAX_VALUE;
|
|
||||||
int end = Integer.MIN_VALUE;
|
|
||||||
|
|
||||||
for (int ray = min.getBlockX(); ray <= max.getBlockX(); ray++) {
|
for (int ray = min.getBlockX(); ray <= max.getBlockX(); ray++) {
|
||||||
if (boreContains(source, bore, new IrisBlockVector(ray, finalRayY, rayZ))) {
|
if (boreContains(source, bore, new IrisBlockVector(ray, rayY, rayZ))) {
|
||||||
start = Math.min(ray, start);
|
start = Math.min(ray, start);
|
||||||
end = Math.max(ray, end);
|
end = Math.max(ray, end);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) {
|
|
||||||
for (int i = start; i <= end; i++) {
|
|
||||||
boreCell(source, bore, new IrisBlockVector(i, finalRayY, rayZ), vair, applied);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) {
|
||||||
|
for (int i = start; i <= end; i++) {
|
||||||
|
applied += boreCell(source, bore, new IrisBlockVector(i, rayY, rayZ), vair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Smash Y
|
// Smash Y
|
||||||
for (int rayX = min.getBlockX(); rayX <= max.getBlockX(); rayX++) {
|
for (int rayX = min.getBlockX(); rayX <= max.getBlockX(); rayX++) {
|
||||||
int finalRayX = rayX;
|
for (int rayZ = min.getBlockZ(); rayZ <= max.getBlockZ(); rayZ++) {
|
||||||
burst.queue(() -> {
|
int start = Integer.MAX_VALUE;
|
||||||
for (int rayZ = min.getBlockZ(); rayZ <= max.getBlockZ(); rayZ++) {
|
int end = Integer.MIN_VALUE;
|
||||||
int start = Integer.MAX_VALUE;
|
|
||||||
int end = Integer.MIN_VALUE;
|
|
||||||
|
|
||||||
for (int ray = min.getBlockY(); ray <= max.getBlockY(); ray++) {
|
for (int ray = min.getBlockY(); ray <= max.getBlockY(); ray++) {
|
||||||
if (boreContains(source, bore, new IrisBlockVector(finalRayX, ray, rayZ))) {
|
if (boreContains(source, bore, new IrisBlockVector(rayX, ray, rayZ))) {
|
||||||
start = Math.min(ray, start);
|
start = Math.min(ray, start);
|
||||||
end = Math.max(ray, end);
|
end = Math.max(ray, end);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) {
|
|
||||||
for (int i = start; i <= end; i++) {
|
|
||||||
boreCell(source, bore, new IrisBlockVector(finalRayX, i, rayZ), vair, applied);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) {
|
||||||
|
for (int i = start; i <= end; i++) {
|
||||||
|
applied += boreCell(source, bore, new IrisBlockVector(rayX, i, rayZ), vair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Smash Z
|
// Smash Z
|
||||||
for (int rayX = min.getBlockX(); rayX <= max.getBlockX(); rayX++) {
|
for (int rayX = min.getBlockX(); rayX <= max.getBlockX(); rayX++) {
|
||||||
int finalRayX = rayX;
|
for (int rayY = min.getBlockY(); rayY <= max.getBlockY(); rayY++) {
|
||||||
burst.queue(() -> {
|
int start = Integer.MAX_VALUE;
|
||||||
for (int rayY = min.getBlockY(); rayY <= max.getBlockY(); rayY++) {
|
int end = Integer.MIN_VALUE;
|
||||||
int start = Integer.MAX_VALUE;
|
|
||||||
int end = Integer.MIN_VALUE;
|
|
||||||
|
|
||||||
for (int ray = min.getBlockZ(); ray <= max.getBlockZ(); ray++) {
|
for (int ray = min.getBlockZ(); ray <= max.getBlockZ(); ray++) {
|
||||||
if (boreContains(source, bore, new IrisBlockVector(finalRayX, rayY, ray))) {
|
if (boreContains(source, bore, new IrisBlockVector(rayX, rayY, ray))) {
|
||||||
start = Math.min(ray, start);
|
start = Math.min(ray, start);
|
||||||
end = Math.max(ray, end);
|
end = Math.max(ray, end);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) {
|
|
||||||
for (int i = start; i <= end; i++) {
|
|
||||||
boreCell(source, bore, new IrisBlockVector(finalRayX, rayY, i), vair, applied);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
burst.complete();
|
if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) {
|
||||||
|
for (int i = start; i <= end; i++) {
|
||||||
self.writeLock.lock();
|
applied += boreCell(source, bore, new IrisBlockVector(rayX, rayY, i), vair);
|
||||||
try {
|
}
|
||||||
if (!self.smartBored) {
|
}
|
||||||
bore.forEach((v, s) -> self.blocks.computeIfAbsent(v, (vv) -> s));
|
|
||||||
self.smartBored = true;
|
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
self.writeLock.unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IrisLogging.debug("Smart Bore: " + self.getLoadKey() + " in " + Form.duration(p.getMilliseconds(), 2) + " (" + Form.f(applied.get()) + ")");
|
source.putAll(bore);
|
||||||
|
self.smartBored = true;
|
||||||
|
|
||||||
|
IrisLogging.debug("Smart Bore: " + self.getLoadKey() + " in " + Form.duration(p.getMilliseconds(), 2) + " (" + Form.f(applied) + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean boreContains(VectorMap<PlatformBlockState> source, VectorMap<PlatformBlockState> bore, IrisBlockVector v) {
|
private static boolean boreContains(VectorMap<PlatformBlockState> source, VectorMap<PlatformBlockState> bore, IrisBlockVector v) {
|
||||||
return source.containsKey(v) || bore.containsKey(v);
|
return source.containsKey(v) || bore.containsKey(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void boreCell(VectorMap<PlatformBlockState> source, VectorMap<PlatformBlockState> bore, IrisBlockVector v, PlatformBlockState vair, AtomicInteger applied) {
|
private static int boreCell(VectorMap<PlatformBlockState> source, VectorMap<PlatformBlockState> bore, IrisBlockVector v, PlatformBlockState vair) {
|
||||||
PlatformBlockState existing = source.get(v);
|
PlatformBlockState existing = source.get(v);
|
||||||
|
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
if (vair.equals(bore.get(v))) {
|
if (vair.equals(bore.get(v))) {
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bore.computeIfAbsent(v, (vv) -> vair);
|
bore.put(v, vair);
|
||||||
} else if (vair.equals(existing)) {
|
} else if (vair.equals(existing)) {
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
applied.getAndIncrement();
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void shrinkwrap(IrisObject self) {
|
static void shrinkwrap(IrisObject self) {
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package art.arcane.iris.engine.object;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.IrisPlatform;
|
||||||
|
import art.arcane.iris.spi.IrisPlatforms;
|
||||||
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
|
import art.arcane.iris.spi.PlatformRegistries;
|
||||||
|
import art.arcane.iris.util.common.math.IrisBlockVector;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class IrisObjectShapingTest {
|
||||||
|
@Before
|
||||||
|
public void bindPlatform() {
|
||||||
|
IrisPlatforms.unbind();
|
||||||
|
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||||
|
Map<String, PlatformBlockState> states = new HashMap<>();
|
||||||
|
when(registries.block(anyString())).thenAnswer(invocation -> states.computeIfAbsent(
|
||||||
|
invocation.getArgument(0), key -> mock(PlatformBlockState.class)));
|
||||||
|
IrisPlatform platform = mock(IrisPlatform.class);
|
||||||
|
when(platform.registries()).thenReturn(registries);
|
||||||
|
IrisPlatforms.bind(platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void unbindPlatform() {
|
||||||
|
IrisPlatforms.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void smartBoreMatchesOrderedAxisClosureAndPreservesOriginalBlocks() {
|
||||||
|
Random random = new Random(617431L);
|
||||||
|
PlatformBlockState stone = mock(PlatformBlockState.class);
|
||||||
|
for (int fixture = 0; fixture < 24; fixture++) {
|
||||||
|
int offset = switch (fixture % 3) {
|
||||||
|
case 0 -> -1050;
|
||||||
|
case 1 -> 1020;
|
||||||
|
default -> -3;
|
||||||
|
};
|
||||||
|
IrisObject object = new IrisObject(6, 6, 6);
|
||||||
|
Map<Cell, PlatformBlockState> expected = new HashMap<>();
|
||||||
|
for (int index = 0; index < 36; index++) {
|
||||||
|
Cell cell = new Cell(offset + random.nextInt(6), offset + random.nextInt(6), offset + random.nextInt(6));
|
||||||
|
PlatformBlockState state = switch (index % 3) {
|
||||||
|
case 0 -> IrisObject.States.AIR;
|
||||||
|
case 1 -> IrisObject.States.VAIR;
|
||||||
|
default -> stone;
|
||||||
|
};
|
||||||
|
object.getBlocks().put(cell.vector(), state);
|
||||||
|
expected.put(cell, state);
|
||||||
|
}
|
||||||
|
for (int axis = 0; axis < 3; axis++) {
|
||||||
|
closeAxis(expected, axis);
|
||||||
|
}
|
||||||
|
|
||||||
|
IrisObjectShaping.ensureSmartBored(object);
|
||||||
|
|
||||||
|
assertTrue(object.isSmartBored());
|
||||||
|
assertEquals("fixture " + fixture, expected.size(), object.getBlocks().size());
|
||||||
|
for (Map.Entry<Cell, PlatformBlockState> entry : expected.entrySet()) {
|
||||||
|
assertSame(entry.getValue(), object.getBlocks().get(entry.getKey().vector()));
|
||||||
|
}
|
||||||
|
long revision = object.getBlocks().modificationRevision();
|
||||||
|
IrisObjectShaping.ensureSmartBored(object);
|
||||||
|
assertEquals(revision, object.getBlocks().modificationRevision());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void emptySmartBoreCompletesWithoutAddingBlocks() {
|
||||||
|
IrisObject object = new IrisObject(1, 1, 1);
|
||||||
|
|
||||||
|
IrisObjectShaping.ensureSmartBored(object);
|
||||||
|
|
||||||
|
assertTrue(object.isSmartBored());
|
||||||
|
assertTrue(object.getBlocks().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void closeAxis(Map<Cell, PlatformBlockState> blocks, int axis) {
|
||||||
|
List<Cell> occupied = new ArrayList<>(blocks.keySet());
|
||||||
|
for (Cell start : occupied) {
|
||||||
|
for (Cell end : occupied) {
|
||||||
|
if (!start.with(axis, 0).equals(end.with(axis, 0))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int position = start.coordinate(axis); position <= end.coordinate(axis); position++) {
|
||||||
|
blocks.putIfAbsent(start.with(axis, position), IrisObject.States.VAIR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Cell(int x, int y, int z) {
|
||||||
|
private int coordinate(int axis) {
|
||||||
|
return switch (axis) {
|
||||||
|
case 0 -> x;
|
||||||
|
case 1 -> y;
|
||||||
|
default -> z;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cell with(int axis, int coordinate) {
|
||||||
|
return switch (axis) {
|
||||||
|
case 0 -> new Cell(coordinate, y, z);
|
||||||
|
case 1 -> new Cell(x, coordinate, z);
|
||||||
|
default -> new Cell(x, y, coordinate);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private IrisBlockVector vector() {
|
||||||
|
return new IrisBlockVector(x, y, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user