switch to builder pattern for block state cached data

This commit is contained in:
Leijurv
2021-05-10 02:18:59 -07:00
parent 5c85d0cfd1
commit efb0afcd6c
9 changed files with 290 additions and 119 deletions

View File

@@ -126,6 +126,9 @@ public class Baritone implements IBaritone {
Main.main();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Throwable th) {
th.printStackTrace();
throw th;
}
}

View File

@@ -18,7 +18,7 @@
package baritone.builder;
import javax.annotation.Nullable;
import java.util.*;
import java.util.List;
/**
* Information about an IBlockState
@@ -27,72 +27,48 @@ import java.util.*;
*/
public final class BlockStateCachedData {
private static final BlockStateCachedData[] PER_STATE = Main.DATA_PROVIDER.all();
public static final BlockStateCachedData SCAFFOLDING = new BlockStateCachedData(false, true, true, Half.EITHER, false);
private static final BlockStateCachedData[] PER_STATE = Main.DATA_PROVIDER.allNullable();
public static final BlockStateCachedData SCAFFOLDING = new BlockStateCachedData(new BlockStateCachedDataBuilder().normalFullBlock());
public final boolean canWalkOn;
public final boolean fullyWalkableTop;
public final boolean isAir;
public final boolean mustSneakWhenPlacingAgainstMe;
private final boolean[] presentsTopHalfFaceForPlacement;
private final boolean[] presentsBottomHalfFaceForPlacement;
private final List<BlockStatePlacementOption> options;
public final List<BlockStatePlacementOption> options;
public static BlockStateCachedData get(int state) {
return PER_STATE[state];
}
public BlockStateCachedData(boolean isAir, boolean canPlaceAgainstAtAll, boolean canWalkOn, Half half, boolean mustSneakWhenPlacingAgainstMe) {
this.isAir = isAir;
this.canWalkOn = canWalkOn;
this.mustSneakWhenPlacingAgainstMe = mustSneakWhenPlacingAgainstMe;
this.options = Collections.unmodifiableList(calcOptions(canPlaceAgainstAtAll, half));
this.presentsTopHalfFaceForPlacement = new boolean[Face.VALUES.length];
this.presentsBottomHalfFaceForPlacement = new boolean[Face.VALUES.length];
setupFacesPresented(canPlaceAgainstAtAll, half);
if (mustSneakWhenPlacingAgainstMe && half != Half.EITHER) {
throw new IllegalArgumentException();
}
}
public BlockStateCachedData(BlockStateCachedDataBuilder builder) {
builder.sanityCheck();
this.isAir = builder.isAir();
this.fullyWalkableTop = builder.isFullyWalkableTop();
this.mustSneakWhenPlacingAgainstMe = builder.isMustSneakWhenPlacingAgainstMe();
this.options = builder.howCanIBePlaced();
private void setupFacesPresented(boolean canPlaceAgainstAtAll, Half half) {
if (!canPlaceAgainstAtAll) {
return;
}
Arrays.fill(presentsBottomHalfFaceForPlacement, true);
Arrays.fill(presentsTopHalfFaceForPlacement, true);
// TODO support case for placing against nub of stair on the one faced side
switch (half) {
case EITHER: {
return;
}
case TOP: {
// i am a top slab, or an upside down stair
presentsBottomHalfFaceForPlacement[Face.DOWN.index] = false;
presentsTopHalfFaceForPlacement[Face.DOWN.index] = false;
for (Face face : Face.HORIZONTALS) {
presentsBottomHalfFaceForPlacement[face.index] = false; // top slab = can't place against the bottom half
}
break;
}
case BOTTOM: {
// i am a bottom slab, or an normal stair
presentsBottomHalfFaceForPlacement[Face.UP.index] = false;
presentsTopHalfFaceForPlacement[Face.UP.index] = false;
for (Face face : Face.HORIZONTALS) {
presentsTopHalfFaceForPlacement[face.index] = false; // bottom slab = can't place against the top half
}
break;
}
}
boolean[][] presented = builder.facesIPresentForPlacementAgainst();
this.presentsTopHalfFaceForPlacement = presented[1];
this.presentsBottomHalfFaceForPlacement = presented[0];
}
@Nullable
private PlaceAgainstData presentsFace(Face face, Half half) {
if ((face == Face.UP || face == Face.DOWN) && half != Half.EITHER) {
public PlaceAgainstData tryAgainstMe(BlockStatePlacementOption placement) {
if (Main.fakePlacementForPerformanceTesting) {
return Main.RAND.nextInt(10) < 8 ? PlaceAgainstData.EITHER : null;
}
Face myFace = placement.against.opposite();
Half theirHalf = placement.half;
if ((myFace == Face.UP || myFace == Face.DOWN) && theirHalf != Half.EITHER) {
throw new IllegalStateException();
}
boolean top = presentsTopHalfFaceForPlacement[face.index] && (half == Half.EITHER || half == Half.TOP);
boolean bottom = presentsBottomHalfFaceForPlacement[face.index] && (half == Half.EITHER || half == Half.BOTTOM);
boolean top = presentsTopHalfFaceForPlacement[myFace.index] && (theirHalf == Half.EITHER || theirHalf == Half.TOP);
boolean bottom = presentsBottomHalfFaceForPlacement[myFace.index] && (theirHalf == Half.EITHER || theirHalf == Half.BOTTOM);
Half intersectedHalf; // the half that both we present, and they accept. not necessarily equal to either. slab-against-block and block-against-slab will both have this as top/bottom, not either.
if (top && bottom) {
intersectedHalf = Half.EITHER;
@@ -105,48 +81,4 @@ public final class BlockStateCachedData {
}
return PlaceAgainstData.get(intersectedHalf, mustSneakWhenPlacingAgainstMe);
}
private List<BlockStatePlacementOption> calcOptions(boolean canPlaceAgainstAtAll, Half half) {
if (canPlaceAgainstAtAll) {
List<BlockStatePlacementOption> ret = new ArrayList<>();
for (Face face : Face.VALUES) {
if (Main.STRICT_Y && face == Face.UP) {
continue;
}
Half overrideHalf = half;
if (face == Face.DOWN) {
if (half == Half.TOP) {
continue;
} else {
overrideHalf = Half.EITHER;
}
}
if (face == Face.UP) {
if (half == Half.BOTTOM) {
continue;
} else {
overrideHalf = Half.EITHER;
}
}
ret.add(BlockStatePlacementOption.get(face, overrideHalf, Optional.empty()));
}
return ret;
}
return Collections.emptyList();
}
@Nullable
public PlaceAgainstData canBeDoneAgainstMe(BlockStatePlacementOption placement) {
if (Main.fakePlacementForPerformanceTesting) {
return Main.RAND.nextInt(10) < 8 ? PlaceAgainstData.EITHER : null;
}
Face myFace = placement.against.opposite();
return presentsFace(myFace, placement.half);
}
public List<BlockStatePlacementOption> placementOptions() {
return options;
}
}

View File

@@ -0,0 +1,212 @@
/*
* This file is part of Baritone.
*
* Baritone is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Baritone is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
*/
package baritone.builder;
import java.util.*;
public class BlockStateCachedDataBuilder {
// should all these be optionals? like maybe Boolean? that start as null? and each has to be set explicitly?
private boolean isAir;
private boolean canPlaceAgainstMe;
private boolean fullyWalkableTop;
private boolean mustSneakWhenPlacingAgainstMe;
/**
* Examples:
* <p>
* Upside down stairs must be placed against TOP
* <p>
* Bottom slabs must be placed against BOTTOM
* <p>
* Normal blocks must be placed against EITHER
*/
private Half mustBePlacedAgainst = Half.EITHER;
private boolean stair;
private Face playerMustBeFacingInOrderToPlaceMe;
private double height = Double.NaN;
public BlockStateCachedDataBuilder() {
}
public BlockStateCachedDataBuilder normalFullBlock() {
return fullyWalkableTop().height(1).canPlaceAgainstMe();
}
public BlockStateCachedDataBuilder stair(boolean rightsideUp, Face facing) {
stair = true;
playerMustBeFacingInOrderToPlaceMe = facing;
return slab(rightsideUp);
}
public BlockStateCachedDataBuilder slab(boolean bottom) {
mustBePlacedAgainst = bottom ? Half.BOTTOM : Half.TOP;
return bottom ? this : fullyWalkableTop().height(1).canPlaceAgainstMe();
}
/**
* Really just air. This is a fully open block that won't collide with object mouse over raytrace.
*/
public BlockStateCachedDataBuilder air() {
isAir = true;
mustBePlacedAgainst = null;
return this;
}
public boolean isAir() {
return isAir;
}
/**
* does the top face of this block fully support the player from 0.0,0.0 to 1.0,1.0? true for most normal blocks. false for, for example, fences
*/
public BlockStateCachedDataBuilder fullyWalkableTop() {
fullyWalkableTop = true;
return this;
}
public boolean isFullyWalkableTop() {
return fullyWalkableTop;
}
public BlockStateCachedDataBuilder height(double y) {
if (!isFullyWalkableTop()) {
throw new IllegalStateException();
}
height = y;
return this;
}
public double supportedPlayerY() { // e.g. slabs are 0.5, soul sand is 0.875, normal blocks are 1, fences are 1.5
if (!isFullyWalkableTop() || Double.isNaN(height)) {
throw new IllegalStateException(); // e.g. rightside-up stairs aren't fully supporting of the player so this doesn't count for them
}
return height;
}
public BlockStateCachedDataBuilder mustSneakWhenPlacingAgainstMe() {
mustSneakWhenPlacingAgainstMe = true;
return this;
}
public boolean isMustSneakWhenPlacingAgainstMe() {
return mustSneakWhenPlacingAgainstMe;
}
public BlockStateCachedDataBuilder canPlaceAgainstMe() {
canPlaceAgainstMe = true;
return this;
}
public List<BlockStatePlacementOption> howCanIBePlaced() {
if (mustBePlacedAgainst == null) {
return Collections.emptyList();
}
List<BlockStatePlacementOption> ret = new ArrayList<>();
for (Face face : Face.VALUES) {
if (Main.STRICT_Y && face == Face.UP) {
continue;
}
if (playerMustBeFacingInOrderToPlaceMe == face.opposite()) { // obv, this won't happen if playerMustBeFacing is null
continue;
}
Half overrideHalf = mustBePlacedAgainst;
if (face == Face.DOWN) {
if (mustBePlacedAgainst == Half.TOP) {
continue;
} else {
overrideHalf = Half.EITHER;
}
}
if (face == Face.UP) {
if (mustBePlacedAgainst == Half.BOTTOM) {
continue;
} else {
overrideHalf = Half.EITHER;
}
}
ret.add(BlockStatePlacementOption.get(face, overrideHalf, Optional.ofNullable(playerMustBeFacingInOrderToPlaceMe)));
}
return Collections.unmodifiableList(ret);
}
public boolean[][] facesIPresentForPlacementAgainst() {
boolean[] presentsBottomHalfFaceForPlacement = new boolean[Face.VALUES.length];
boolean[] presentsTopHalfFaceForPlacement = new boolean[Face.VALUES.length];
if (canPlaceAgainstMe) {
Arrays.fill(presentsBottomHalfFaceForPlacement, true);
Arrays.fill(presentsTopHalfFaceForPlacement, true);
switch (mustBePlacedAgainst) {
case EITHER: {
break;
}
case TOP: {
// i am a top slab, or an upside down stair
presentsBottomHalfFaceForPlacement[Face.DOWN.index] = false;
presentsTopHalfFaceForPlacement[Face.DOWN.index] = false;
for (Face face : Face.HORIZONTALS) {
if (stair && face == playerMustBeFacingInOrderToPlaceMe) {
continue; // little nub of the stair on the faced side
}
presentsBottomHalfFaceForPlacement[face.index] = false; // top slab = can't place against the bottom half
}
break;
}
case BOTTOM: {
// i am a bottom slab, or an normal stair
presentsBottomHalfFaceForPlacement[Face.UP.index] = false;
presentsTopHalfFaceForPlacement[Face.UP.index] = false;
for (Face face : Face.HORIZONTALS) {
if (stair && face == playerMustBeFacingInOrderToPlaceMe) {
continue; // little nub of the stair on the faced side
}
presentsTopHalfFaceForPlacement[face.index] = false; // bottom slab = can't place against the top half
}
break;
}
}
}
return new boolean[][]{presentsBottomHalfFaceForPlacement, presentsTopHalfFaceForPlacement};
}
public void sanityCheck() {
if (isAir()) {
if (!howCanIBePlaced().isEmpty()) {
throw new IllegalStateException();
}
if (isFullyWalkableTop()) {
throw new IllegalStateException();
}
}
if (mustBePlacedAgainst == null ^ isAir()) {
throw new IllegalStateException();
}
if (isMustSneakWhenPlacingAgainstMe() && mustBePlacedAgainst != Half.EITHER) {
throw new IllegalArgumentException();
}
if (stair ^ (playerMustBeFacingInOrderToPlaceMe != null && mustBePlacedAgainst != Half.EITHER)) {
throw new IllegalStateException();
}
if (isFullyWalkableTop() == Double.isNaN(height)) {
throw new IllegalStateException();
}
}
static {
new BlockStateCachedDataBuilder().sanityCheck();
}
}

View File

@@ -57,8 +57,8 @@ public class BlockStatePlacementOption {
*/
private static final double LOOSE_CENTER_DISTANCE = 0.15;
public List<Raytracer.Raytrace> computeTraceOptions(Half againstHalf, int playerSupportingX, double playerEyeY, int playerSupportingZ, PlayerVantage vantage, double blockReachDistance) {
if (againstHalf != half && half != Half.EITHER) { // narrowing is ok (EITHER -> TOP/BOTTOM) but widening isn't (TOP/BOTTOM -> EITHER)
public List<Raytracer.Raytrace> computeTraceOptions(PlaceAgainstData placingAgainst, int playerSupportingX, double playerEyeY, int playerSupportingZ, PlayerVantage vantage, double blockReachDistance) {
if (placingAgainst.half != half && half != Half.EITHER) { // narrowing is ok (EITHER -> TOP/BOTTOM) but widening isn't (TOP/BOTTOM -> EITHER)
throw new IllegalStateException();
}
List<Vec2d> acceptableVantages = new ArrayList<>();
@@ -95,14 +95,13 @@ public class BlockStatePlacementOption {
.map(playerEyeXZ -> new Vec3d(playerEyeXZ.x, playerEyeY, playerEyeXZ.z))
.flatMap(eye ->
Stream.of(FACE_PROJECTION_CACHE[against.index])
.filter(this::hitOk)
.filter(hit -> hitOk(placingAgainst, hit))
.filter(hit -> eye.distSq(hit) < blockReachDistance * blockReachDistance)
.filter(hit -> directionOk(eye, hit))
.<Supplier<Optional<Raytracer.Raytrace>>>map(hit -> () -> Raytracer.runTrace(eye, placeAgainstPos, against.opposite(), hit))
)
.collect(Collectors.toList())
// TODO switch back to parallelStream
.stream() // wrap it like this because flatMap forces .sequential() on the interior child stream, defeating the point
.parallelStream() // wrap it like this because flatMap forces .sequential() on the interior child stream, defeating the point
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
@@ -110,15 +109,15 @@ public class BlockStatePlacementOption {
.collect(Collectors.toList()));
}
private boolean hitOk(Vec3d hit) {
if (half == Half.EITHER) {
private static boolean hitOk(PlaceAgainstData placingAgainst, Vec3d hit) {
if (placingAgainst.half == Half.EITHER) {
return true;
} else if (hit.y == 0.1) {
return half == Half.BOTTOM;
return placingAgainst.half == Half.BOTTOM;
} else if (hit.y == 0.5) {
return false;
} else if (hit.y == 0.9) {
return half == Half.TOP;
return placingAgainst.half == Half.TOP;
} else {
throw new IllegalStateException();
}
@@ -226,7 +225,7 @@ public class BlockStatePlacementOption {
for (PlayerVantage vantage : new PlayerVantage[]{PlayerVantage.STRICT_CENTER, PlayerVantage.LOOSE_CENTER}) {
for (Face playerFacing : new Face[]{Face.NORTH, Face.EAST, Face.WEST}) {
sanity.append(vantage).append(playerFacing);
List<Raytracer.Raytrace> traces = BlockStatePlacementOption.get(Face.NORTH, Half.BOTTOM, Optional.of(playerFacing)).computeTraceOptions(Half.BOTTOM, 1, 1.62, 0, vantage, 4);
List<Raytracer.Raytrace> traces = BlockStatePlacementOption.get(Face.NORTH, Half.BOTTOM, Optional.of(playerFacing)).computeTraceOptions(PlaceAgainstData.get(Half.BOTTOM, false), 1, 1.62, 0, vantage, 4);
sanity.append(traces.size());
sanity.append(" ");
if (!traces.isEmpty()) {

View File

@@ -18,16 +18,21 @@
package baritone.builder;
import java.util.Arrays;
import java.util.Optional;
public interface IBlockStateDataProvider {
int numStates();
BlockStateCachedData get(int i);
Optional<BlockStateCachedDataBuilder> getBuilder(int i);
default BlockStateCachedData[] all() {
default BlockStateCachedData getNullable(int i) {
return getBuilder(i).map(BlockStateCachedData::new).orElse(null);
}
default BlockStateCachedData[] allNullable() {
BlockStateCachedData[] ret = new BlockStateCachedData[numStates()];
Arrays.setAll(ret, this::get);
Arrays.setAll(ret, this::getNullable);
return ret;
}
}

View File

@@ -156,6 +156,9 @@ public class Main {
.collect(Collectors.toList()).parallelStream()
.forEach(x -> System.out.println(x + ""));
}
{
BlockStateCachedData.get(0);
}
{
BlockStatePlacementOption.sanityCheck();
}

View File

@@ -42,7 +42,7 @@ public class PlaceOrderDependencyGraph {
}
private void compute(long pos) {
for (BlockStatePlacementOption option : data(pos).placementOptions()) {
for (BlockStatePlacementOption option : data(pos).options) {
if (Main.STRICT_Y && option.against == Face.UP) {
throw new IllegalStateException();
}
@@ -53,7 +53,7 @@ public class PlaceOrderDependencyGraph {
} else {
against = BlockStateCachedData.SCAFFOLDING;
}
if (against.canBeDoneAgainstMe(option) != null) {
if (against.tryAgainstMe(option) != null) {
edges.set(bitIndex(pos, option.against));
}
}

View File

@@ -17,12 +17,15 @@
package baritone.builder.mc;
import baritone.builder.BlockStateCachedData;
import baritone.builder.Half;
import baritone.builder.BlockStateCachedDataBuilder;
import baritone.builder.Face;
import net.minecraft.block.Block;
import net.minecraft.block.BlockAir;
import net.minecraft.block.BlockSlab;
import net.minecraft.block.BlockStairs;
import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Blocks;
import net.minecraft.util.EnumFacing;
/**
* I expect this class to get extremely complicated.
@@ -31,13 +34,27 @@ import net.minecraft.init.Blocks;
*/
public class BlockStatePropertiesExtractor {
public static BlockStateCachedData getData(IBlockState state) {
public static BlockStateCachedDataBuilder getData(IBlockState state) {
Block block = state.getBlock();
BlockStateCachedDataBuilder builder = new BlockStateCachedDataBuilder();
if (block instanceof BlockAir) {
return new BlockStateCachedData(true, false, false, Half.EITHER, false);
return builder.air();
}
boolean normal = block == Blocks.COBBLESTONE || block == Blocks.DIRT;
return new BlockStateCachedData(false, normal, normal, Half.EITHER, false);
if (block instanceof BlockStairs) {
boolean rightsideUp = state.getValue(BlockStairs.HALF) == BlockStairs.EnumHalf.BOTTOM; // true if normal stair, false if upside down stair
EnumFacing facing = state.getValue(BlockStairs.FACING);
return builder.stair(rightsideUp, Face.fromMC(facing));
}
if (block instanceof BlockSlab) {
if (((BlockSlab) block).isDouble()) {
return builder.normalFullBlock();
}
return builder.slab(state.getValue(BlockSlab.HALF) == BlockSlab.EnumBlockHalf.BOTTOM);
}
if (block == Blocks.COBBLESTONE || block == Blocks.DIRT) {
builder.normalFullBlock();
}
return builder;
}
}

View File

@@ -17,7 +17,7 @@
package baritone.builder.mc;
import baritone.builder.BlockStateCachedData;
import baritone.builder.BlockStateCachedDataBuilder;
import baritone.builder.IBlockStateDataProvider;
import net.minecraft.block.Block;
@@ -31,7 +31,7 @@ public class VanillaBlockStateDataProvider implements IBlockStateDataProvider {
}
@Override
public BlockStateCachedData get(int i) {
return Optional.ofNullable(Block.BLOCK_STATE_IDS.getByValue(i)).map(BlockStatePropertiesExtractor::getData).orElse(null);
public Optional<BlockStateCachedDataBuilder> getBuilder(int i) {
return Optional.ofNullable(Block.BLOCK_STATE_IDS.getByValue(i)).map(BlockStatePropertiesExtractor::getData);
}
}