Compare commits
3 Commits
builder-2
...
builder-2-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54e7473a6e | ||
|
|
da1a5c121f | ||
|
|
1586a7c7a6 |
@@ -18,29 +18,36 @@
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import baritone.builder.utils.com.github.btrekkie.connectivity.MutatingAugmentation;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
|
||||
public class CountingSurface extends NavigableSurface {
|
||||
public CountingSurface(int x, int y, int z) {
|
||||
super(x, y, z, Attachment::new, $ -> new Attachment());
|
||||
super(x, y, z, new MutatingAugmentation() {
|
||||
@Override
|
||||
public void combine(Object value1, Object value2, Object result) {
|
||||
((Attachment) result).combine((Attachment) value1, (Attachment) value2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object newAugmentation() {
|
||||
return new Attachment();
|
||||
}
|
||||
}, $ -> new Attachment());
|
||||
}
|
||||
|
||||
private static class Attachment {
|
||||
public final int surfaceSize;
|
||||
|
||||
public Attachment(Object a, Object b) {
|
||||
this((Attachment) a, (Attachment) b);
|
||||
}
|
||||
|
||||
public Attachment(Attachment a, Attachment b) {
|
||||
this.surfaceSize = a.surfaceSize + b.surfaceSize;
|
||||
}
|
||||
public int surfaceSize;
|
||||
|
||||
public Attachment() {
|
||||
this.surfaceSize = 1;
|
||||
}
|
||||
|
||||
public void combine(Attachment child1, Attachment child2) {
|
||||
surfaceSize = child1.surfaceSize + child2.surfaceSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) { // used as performance optimization in RedBlackNode to avoid augmenting unchanged attachments
|
||||
if (this == o) {
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import baritone.builder.utils.com.github.btrekkie.connectivity.Augmentation;
|
||||
import baritone.builder.utils.com.github.btrekkie.connectivity.ConnGraph;
|
||||
import baritone.builder.utils.com.github.btrekkie.connectivity.MutatingAugmentation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
@@ -37,7 +37,7 @@ public class NavigableSurface {
|
||||
private final Column col1 = new Column();
|
||||
private final Column col2 = new Column();
|
||||
|
||||
public NavigableSurface(int x, int y, int z, Augmentation augmentation, Function<BetterBlockPos, Object> genVertexAugmentation) {
|
||||
public NavigableSurface(int x, int y, int z, MutatingAugmentation augmentation, Function<BetterBlockPos, Object> genVertexAugmentation) {
|
||||
this.bounds = new CuboidBounds(x, y, z);
|
||||
this.blocks = new BlockStateCachedData[bounds.volume()];
|
||||
Arrays.fill(blocks, FakeStates.AIR);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package baritone.builder.utils.com.github.btrekkie.connectivity;
|
||||
|
||||
/**
|
||||
* An Augmentation implementation that wraps a MutatingAugmentation. It recycles (or "pools") previously constructed
|
||||
* combined augmentation objects in order to improve performance. This should be passed to the ConnGraph constructor as
|
||||
* an AugmentationReleaseListener so that it can recycle unused objects.
|
||||
*/
|
||||
class AugmentationPool implements Augmentation, AugmentationReleaseListener {
|
||||
/**
|
||||
* The maximum number of unused objects to store in a pool.
|
||||
*/
|
||||
private static final int CAPACITY = 20;
|
||||
|
||||
/**
|
||||
* The MutatingAugmentation we are wrapping.
|
||||
*/
|
||||
private final MutatingAugmentation mutatingAugmentation;
|
||||
|
||||
/**
|
||||
* A pool of unused objects we may reuse. The array has length CAPACITY, but only the first "size" elements contain
|
||||
* reusable objects. The values in the array after the first "size" are unspecified.
|
||||
*/
|
||||
private Object[] pool = new Object[CAPACITY];
|
||||
|
||||
/**
|
||||
* The number of reusable objects in the pool.
|
||||
*/
|
||||
private int size;
|
||||
|
||||
public AugmentationPool(MutatingAugmentation mutatingAugmentation) {
|
||||
this.mutatingAugmentation = mutatingAugmentation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object combine(Object value1, Object value2) {
|
||||
Object result;
|
||||
if (size == 0) {
|
||||
result = mutatingAugmentation.newAugmentation();
|
||||
} else {
|
||||
size--;
|
||||
result = pool[size];
|
||||
}
|
||||
mutatingAugmentation.combine(value1, value2, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void combinedAugmentationReleased(Object obj) {
|
||||
if (size < CAPACITY) {
|
||||
pool[size] = obj;
|
||||
size++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void vertexAugmentationReleased(Object obj) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package baritone.builder.utils.com.github.btrekkie.connectivity;
|
||||
|
||||
/**
|
||||
* Responds to when ownership of an augmentation object is released. If the number of times that
|
||||
* combinedAugmentationReleased and vertexAugmentationReleased were called on an object is equal to the number of times
|
||||
* that Augmentation.combine returned the object plus the number of times the object was passed to
|
||||
* setVertexAugmentation, then ConnGraph no longer has a reference to the object. Such an object may be recycled.
|
||||
* <p>
|
||||
* Note that a graph may have multiple ownership claims to a given augmentation object, meaning the graph needs to
|
||||
* release the object multiple times before it can be recycled. This could happen if Augmentation.combine returned the
|
||||
* same object multiple times or the object was passed to setVertexAugmentation multiple times.
|
||||
* <p>
|
||||
* See ConnGraph(Augmentation, AugmentationReleaseListener).
|
||||
*/
|
||||
public interface AugmentationReleaseListener {
|
||||
/**
|
||||
* Responds to one ownership claim to the specified combined augmentation object (or previous return value of
|
||||
* Augmentation.combine) being released. "obj" is guaranteed not to be null.
|
||||
* <p>
|
||||
* This may be called from any ConnGraph method that mutates the graph, as well as from optimize().
|
||||
*/
|
||||
public void combinedAugmentationReleased(Object obj);
|
||||
|
||||
/**
|
||||
* Responds to one ownership claim to the specified vertex augmentation object (or previous argument to
|
||||
* setVertexAugmentation) being released. "obj" is guaranteed not to be null.
|
||||
* <p>
|
||||
* This may be called from the following ConnGraph methods: setVertexAugmentation, removeVertexAugmentation, and
|
||||
* clear().
|
||||
*/
|
||||
public void vertexAugmentationReleased(Object obj);
|
||||
}
|
||||
@@ -121,7 +121,12 @@ public class ConnGraph {
|
||||
/**
|
||||
* The augmentation function for the graph, if any.
|
||||
*/
|
||||
private final Augmentation augmentation;
|
||||
final Augmentation augmentation;
|
||||
|
||||
/**
|
||||
* The AugmentationReleaseListener listening to release of ownership of augmentation objects, if any.
|
||||
*/
|
||||
final AugmentationReleaseListener augmentationReleaseListener;
|
||||
|
||||
/**
|
||||
* A map from each vertex in this graph to information about the vertex in this graph. If a vertex has no adjacent
|
||||
@@ -142,13 +147,93 @@ public class ConnGraph {
|
||||
*/
|
||||
public ConnGraph() {
|
||||
augmentation = null;
|
||||
augmentationReleaseListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an augmented ConnGraph, using the specified function to combine augmentation values. See
|
||||
* ConnGraph(MutatingAugmentation) regarding an alternative that may be more performant.
|
||||
*/
|
||||
public ConnGraph(Augmentation augmentation) {
|
||||
this.augmentation = augmentation;
|
||||
augmentationReleaseListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an augmented ConnGraph, using the specified function to combine augmentation values.
|
||||
* <p>
|
||||
* While ConnGraph(Augmentation) is a little more convenient and elegant, ConnGraph(MutatingAugmentation) should be
|
||||
* more performant, provided that the Augmentation constructs a new object every time "combine" is called. This is
|
||||
* because ConnGraph(MutatingAugmentation) is able to recycle (or "pool") augmentation objects that are no longer in
|
||||
* use, thereby reducing the number of object allocations.
|
||||
* <p>
|
||||
* However, not all Augmentations construct a new object every time "combine" is called. If the augmentation objects
|
||||
* are Booleans, then an Augmentation would return a cached Boolean object each time "combine" is called (assuming
|
||||
* it uses normal Java boxing conversions). So ConnGraph(Augmentation) is actually more efficient in the case of
|
||||
* Booleans. In the case of Integers, Augmentation.combine may or may not return a cached Integer object,
|
||||
* considering that the Integer class caches Integer objects for the range from -128 to 127. So
|
||||
* ConnGraph(MutatingAugmentation) may or may not be more performant in the case of Integers. It is possible to
|
||||
* combine the benefits of caching and object reuse by using the ConnGraph(Augmentation,
|
||||
* AugmentationReleaseListener) constructor.
|
||||
* <p>
|
||||
* Note that combined augmentation values returned by getComponentAugmentation may later be recycled and their
|
||||
* contents changed. A return value of getComponentAugmentation should only be considered valid until the next time
|
||||
* the graph is mutated or optimize() is called on it.
|
||||
* <p>
|
||||
* Only combined augmentation objects are automatically recycled. Vertex augmentation objects passed to
|
||||
* setVertexAugmentation are not. If you want to reduce the number of objects allocated for setting vertex
|
||||
* augmentations, you can do so manually. For example:
|
||||
* <p>
|
||||
* class IntWrapper {
|
||||
* public int value;
|
||||
* }
|
||||
* <p>
|
||||
* class ThingThatUsesConnGraph {
|
||||
* private IntWrapper augmentation;
|
||||
* <p>
|
||||
* private void setVertexAugmentation(ConnGraph graph, ConnVertex vertex, int value) {
|
||||
* if (augmentation == null) {
|
||||
* augmentation = new IntWrapper();
|
||||
* }
|
||||
* augmentation.value = value;
|
||||
* augmentation = (IntWrapper)graph.setVertexAugmentation(vertex, augmentation);
|
||||
* }
|
||||
* <p>
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
public ConnGraph(Augmentation augmentation) {
|
||||
public ConnGraph(MutatingAugmentation mutatingAugmentation) {
|
||||
AugmentationPool pool = new AugmentationPool(mutatingAugmentation);
|
||||
augmentation = pool;
|
||||
augmentationReleaseListener = pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an augmented ConnGraph, using the specified function to combine augmentation values.
|
||||
* <p>
|
||||
* The AugmentationReleaseListener can be used to track when an augmentation object is no longer stored in this
|
||||
* graph and the object may be recycled. For example, if we are augmenting vertices with integers, we could create
|
||||
* an IntWrapper class with an int field to store the augmentations. The first time Augmentation.combine is called,
|
||||
* we would construct a new IntWrapper instance and return that. Later on, the AugmentationReleaseListener might
|
||||
* tell us that the ConnGraph no longer needs the IntWrapper object. So in a subsequent call to
|
||||
* Augmentation.combine, instead of constructing a new IntWrapper, we could set the old IntWrapper instance's int
|
||||
* field and return that IntWrapper.
|
||||
* <p>
|
||||
* Note that once an augmentation object is recycled, its old contents are lost. For example, if we are recycling
|
||||
* combined augmentation objects, then normally, we should only consider a return value of getComponentAugmentation
|
||||
* to be valid until the next time the graph is mutated or optimize() is called. After that point, the contents of
|
||||
* the return value of getComponentAugmentation may have changed.
|
||||
* <p>
|
||||
* ConnGraph(Augmentation, AugmentationReleaseListener) can be used to improve performance by minimizing the number
|
||||
* of object allocations. Compared to ConnGraph(MutatingAugmentation), ConnGraph(Augmentation,
|
||||
* AugmentationReleaseListener) offers more control, so it may be possible to improve performance using
|
||||
* ConnGraph(Augmentation, AugmentationReleaseListener). For example, this constructor could be used to combine
|
||||
* caching and object reuse techniques, as suggested in the comments for ConnGraph(MutatingAugmentation).
|
||||
*/
|
||||
// TODO: Is this useful enough to be worth exposing publicly?
|
||||
public ConnGraph(Augmentation augmentation, AugmentationReleaseListener augmentationReleaseListener) {
|
||||
this.augmentation = augmentation;
|
||||
this.augmentationReleaseListener = augmentationReleaseListener;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,7 +243,7 @@ public class ConnGraph {
|
||||
if (augmentation == null) {
|
||||
throw new RuntimeException(
|
||||
"You may only call augmentation-related methods on ConnGraph if the graph is augmented, i.e. if an " +
|
||||
"Augmentation was passed to the constructor");
|
||||
"Augmentation or MutatingAugmentation was passed to the constructor");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +265,7 @@ public class ConnGraph {
|
||||
}
|
||||
|
||||
EulerTourVertex eulerTourVertex = new EulerTourVertex();
|
||||
EulerTourNode node = new EulerTourNode(eulerTourVertex, augmentation);
|
||||
EulerTourNode node = new EulerTourNode(eulerTourVertex, this);
|
||||
eulerTourVertex.arbitraryVisit = node;
|
||||
node.left = EulerTourNode.LEAF;
|
||||
node.right = EulerTourNode.LEAF;
|
||||
@@ -483,7 +568,7 @@ public class ConnGraph {
|
||||
root = max.remove();
|
||||
EulerTourNode[] splitRoots = root.split(vertex2.arbitraryVisit);
|
||||
root = splitRoots[1].concatenate(splitRoots[0]);
|
||||
EulerTourNode newNode = new EulerTourNode(vertex2, root.augmentationFunc);
|
||||
EulerTourNode newNode = new EulerTourNode(vertex2, root.graph);
|
||||
newNode.left = EulerTourNode.LEAF;
|
||||
newNode.right = EulerTourNode.LEAF;
|
||||
newNode.isRed = true;
|
||||
@@ -497,7 +582,7 @@ public class ConnGraph {
|
||||
EulerTourNode[] splitRoots = vertex1.arbitraryVisit.root().split(vertex1.arbitraryVisit);
|
||||
EulerTourNode before = splitRoots[0];
|
||||
EulerTourNode after = splitRoots[1];
|
||||
EulerTourNode newNode = new EulerTourNode(vertex1, root.augmentationFunc);
|
||||
EulerTourNode newNode = new EulerTourNode(vertex1, root.graph);
|
||||
before.concatenate(root, newNode).concatenate(after);
|
||||
return new EulerTourEdge(newNode, max);
|
||||
}
|
||||
@@ -960,6 +1045,12 @@ public class ConnGraph {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (augmentationReleaseListener != null && oldAugmentation != null) {
|
||||
// Note that oldAugmentation must be released after the calls to augment() earlier in the method, in order
|
||||
// to ensure that we do not change its contents before returning it
|
||||
augmentationReleaseListener.vertexAugmentationReleased(oldAugmentation);
|
||||
}
|
||||
return oldAugmentation;
|
||||
}
|
||||
|
||||
@@ -995,6 +1086,12 @@ public class ConnGraph {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (augmentationReleaseListener != null && oldAugmentation != null) {
|
||||
// Note that oldAugmentation must be released after the calls to augment() earlier in the method, in order
|
||||
// to ensure that we do not change its contents before returning it
|
||||
augmentationReleaseListener.vertexAugmentationReleased(oldAugmentation);
|
||||
}
|
||||
return oldAugmentation;
|
||||
}
|
||||
|
||||
@@ -1075,11 +1172,39 @@ public class ConnGraph {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases ownership of all of the augmentation values stored in this graph, by calling
|
||||
* combinedAugmentationReleased and vertexAugmentationReleased on augmentationReleaseListener. This assumes that
|
||||
* augmentationReleaseListener is non-null.
|
||||
*/
|
||||
private void releaseAugmentations() {
|
||||
for (VertexInfo info : vertexInfo.values()) {
|
||||
if (info.vertex.augmentation != null) {
|
||||
augmentationReleaseListener.vertexAugmentationReleased(info.vertex.augmentation);
|
||||
}
|
||||
|
||||
EulerTourNode node = info.vertex.arbitraryVisit;
|
||||
do {
|
||||
if (node.ownsAugmentation) {
|
||||
augmentationReleaseListener.combinedAugmentationReleased(node.augmentation);
|
||||
}
|
||||
node = node.successor();
|
||||
if (node == null) {
|
||||
node = info.vertex.arbitraryVisit.root().min();
|
||||
}
|
||||
} while (node.vertex.arbitraryVisit != node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears this graph, by removing all edges and vertices, and removing all augmentation information from the
|
||||
* vertices.
|
||||
*/
|
||||
public void clear() {
|
||||
if (augmentationReleaseListener != null && augmentation != null) {
|
||||
releaseAugmentations();
|
||||
}
|
||||
|
||||
// Note that we construct a new HashMap rather than calling vertexInfo.clear() in order to ensure a reduction in
|
||||
// space
|
||||
vertexInfo = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
@@ -39,16 +39,13 @@ class EulerTourNode extends RedBlackNode<EulerTourNode> {
|
||||
*/
|
||||
public boolean hasForestEdge;
|
||||
|
||||
/**
|
||||
* The combining function for combining user-provided augmentations. augmentationFunc is null if this node is not in
|
||||
* the highest level.
|
||||
*/
|
||||
public final Augmentation augmentationFunc;
|
||||
/** The graph this belongs to. "graph" is null instead if this node is not in the highest level. */
|
||||
public final ConnGraph graph;
|
||||
|
||||
/**
|
||||
* The combined augmentation for the subtree rooted at this node. This is the result of combining the augmentation
|
||||
* values node.vertex.augmentation for all nodes "node" in the subtree rooted at this node for which
|
||||
* node.vertex.arbitraryVisit == node, using augmentationFunc. This is null if hasAugmentation is false.
|
||||
* node.vertex.arbitraryVisit == node, using graph.augmentation. This is null if hasAugmentation is false.
|
||||
*/
|
||||
public Object augmentation;
|
||||
|
||||
@@ -59,9 +56,17 @@ class EulerTourNode extends RedBlackNode<EulerTourNode> {
|
||||
*/
|
||||
public boolean hasAugmentation;
|
||||
|
||||
public EulerTourNode(EulerTourVertex vertex, Augmentation augmentationFunc) {
|
||||
/**
|
||||
* Whether this node "owns" "augmentation", with respect to graph.augmentationReleaseListener. A node owns a
|
||||
* combined augmentation if the node obtained it by calling Augmentation.combine, as opposed to copying a reference
|
||||
* to vertex.augmentation, left.augmentation, or right.augmentation. This is false if
|
||||
* graph.augmentationReleaseListener or "augmentation" is null.
|
||||
*/
|
||||
public boolean ownsAugmentation;
|
||||
|
||||
public EulerTourNode(EulerTourVertex vertex, ConnGraph graph) {
|
||||
this.vertex = vertex;
|
||||
this.augmentationFunc = augmentationFunc;
|
||||
this.graph = graph;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,40 +91,71 @@ class EulerTourNode extends RedBlackNode<EulerTourNode> {
|
||||
public boolean augment() {
|
||||
int newSize = left.size + right.size + 1;
|
||||
boolean augmentedFlags = augmentFlags();
|
||||
|
||||
Object newAugmentation = null;
|
||||
boolean newHasAugmentation = false;
|
||||
if (augmentationFunc != null) {
|
||||
if (left.hasAugmentation) {
|
||||
newAugmentation = left.augmentation;
|
||||
newHasAugmentation = true;
|
||||
}
|
||||
if (vertex.hasAugmentation && vertex.arbitraryVisit == this) {
|
||||
if (newHasAugmentation) {
|
||||
newAugmentation = augmentationFunc.combine(newAugmentation, vertex.augmentation);
|
||||
} else {
|
||||
newAugmentation = vertex.augmentation;
|
||||
newHasAugmentation = true;
|
||||
}
|
||||
}
|
||||
if (right.hasAugmentation) {
|
||||
if (newHasAugmentation) {
|
||||
newAugmentation = augmentationFunc.combine(newAugmentation, right.augmentation);
|
||||
} else {
|
||||
newAugmentation = right.augmentation;
|
||||
newHasAugmentation = true;
|
||||
}
|
||||
if (graph == null || graph.augmentation == null) {
|
||||
if (newSize == size && !augmentedFlags) {
|
||||
return false;
|
||||
} else {
|
||||
size = newSize;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
AugmentationReleaseListener releaseListener = graph.augmentationReleaseListener;
|
||||
Object newAugmentation = null;
|
||||
int valueCount = 0;
|
||||
if (left.hasAugmentation) {
|
||||
newAugmentation = left.augmentation;
|
||||
valueCount = 1;
|
||||
}
|
||||
if (vertex.hasAugmentation && vertex.arbitraryVisit == this) {
|
||||
if (valueCount == 0) {
|
||||
newAugmentation = vertex.augmentation;
|
||||
} else {
|
||||
newAugmentation = graph.augmentation.combine(newAugmentation, vertex.augmentation);
|
||||
}
|
||||
valueCount++;
|
||||
}
|
||||
if (right.hasAugmentation) {
|
||||
if (valueCount == 0) {
|
||||
newAugmentation = right.augmentation;
|
||||
} else {
|
||||
Object tempAugmentation = newAugmentation;
|
||||
newAugmentation = graph.augmentation.combine(newAugmentation, right.augmentation);
|
||||
if (valueCount >= 2 && releaseListener != null && tempAugmentation != null) {
|
||||
releaseListener.combinedAugmentationReleased(tempAugmentation);
|
||||
}
|
||||
}
|
||||
valueCount++;
|
||||
}
|
||||
|
||||
boolean newHasAugmentation = valueCount > 0;
|
||||
boolean newOwnsAugmentation = valueCount >= 2 && releaseListener != null && newAugmentation != null;
|
||||
if (newSize == size && !augmentedFlags && hasAugmentation == newHasAugmentation &&
|
||||
(newAugmentation != null ? newAugmentation.equals(augmentation) : augmentation == null)) {
|
||||
if (newOwnsAugmentation) {
|
||||
releaseListener.combinedAugmentationReleased(newAugmentation);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
if (ownsAugmentation) {
|
||||
releaseListener.combinedAugmentationReleased(augmentation);
|
||||
}
|
||||
size = newSize;
|
||||
augmentation = newAugmentation;
|
||||
hasAugmentation = newHasAugmentation;
|
||||
ownsAugmentation = newOwnsAugmentation;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeWithoutGettingRoot() {
|
||||
super.removeWithoutGettingRoot();
|
||||
if (ownsAugmentation) {
|
||||
graph.augmentationReleaseListener.combinedAugmentationReleased(augmentation);
|
||||
augmentation = null;
|
||||
hasAugmentation = false;
|
||||
ownsAugmentation = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package baritone.builder.utils.com.github.btrekkie.connectivity;
|
||||
|
||||
/**
|
||||
* A combining function for taking the augmentations associated with a set of ConnVertices and reducing them to a single
|
||||
* result. MutatingAugmentation has a binary operation "combine" that takes two values, combines them using a function
|
||||
* C, and stores the result in a mutable object. For example:
|
||||
* <p>
|
||||
* class IntWrapper {
|
||||
* public int value;
|
||||
* }
|
||||
* <p>
|
||||
* class Sum implements MutatingAugmentation {
|
||||
* public void combine(Object value1, Object value2, Object result) {
|
||||
* ((IntWrapper)result).value = ((IntWrapper)value1).value + ((IntWrapper)value2).value;
|
||||
* }
|
||||
* <p>
|
||||
* public Object newAugmentation() {
|
||||
* return new IntWrapper();
|
||||
* }
|
||||
* }
|
||||
* <p>
|
||||
* Given vertices with augmentations A1, A2, A3, and A4, the combined result may be obtained by computing
|
||||
* C(C(C(A1, A2), A3), A4). In order for an augmentation result to be meaningful, the combining function must be
|
||||
* commutative, meaning C(x, y) is equivalent to C(y, x), and associative, meaning C(x, C(y, z)) is equivalent to
|
||||
* C(C(x, y), z).
|
||||
* <p>
|
||||
* If a ConnGraph represents a game map, then one example of an augmentation would be the amount of gold accessible from
|
||||
* a certain location. Each vertex would be augmented with the amount of gold in that location, and the combining
|
||||
* function would add the two amounts of gold passed in as arguments. Another example would be the strongest monster
|
||||
* that can reach a particular location. Each vertex with at least one monster would be augmented with a reference to
|
||||
* the strongest monster at that location, and the combining function would take the stronger of the two monsters passed
|
||||
* in as arguments. A third example would be the number of locations accessible from a given vertex. Each vertex would
|
||||
* be augmented with the number 1, and the combining function would add the two numbers of vertices passed in as
|
||||
* arguments.
|
||||
* <p>
|
||||
* ConnGraph treats two augmentation values X and Y as interchangeable if they are equal, as in
|
||||
* X != null ? X.equals(Y) : Y == null. The same holds for two combined augmentation values, and for one combined
|
||||
* augmentation value and one augmentation value.
|
||||
* <p>
|
||||
* See the comments for ConnGraph.
|
||||
*/
|
||||
public interface MutatingAugmentation {
|
||||
/**
|
||||
* Computes the result of combining value1 and value2 into one, and stores it in "result".
|
||||
*/
|
||||
public void combine(Object value1, Object value2, Object result);
|
||||
|
||||
/**
|
||||
* Constructs and returns a new augmentation object that may subsequently be passed to "combine". The initial
|
||||
* contents of the object are ignored.
|
||||
*/
|
||||
public Object newAugmentation();
|
||||
}
|
||||
@@ -31,12 +31,12 @@ Thoughts concerning optimization:
|
||||
(but interestingly, not O(log log N)). However, I think this would be
|
||||
significantly slower than a B-tree in practice.
|
||||
- If I don't implement the B-tree optimization, there are a couple of small
|
||||
optimizations I could try. First, I could move the field
|
||||
EulerTourNode.augmentationFunc to EulerTourVertex, in order to save a little
|
||||
bit of space. Second, I could create EulerTourNode and EulerTourVertex
|
||||
subclasses specifically for the top level, and offload the fields related to
|
||||
user augmentation to those subclasses. These changes seem inelegant, but it
|
||||
might be worth checking the effect they have on performance.
|
||||
optimizations I could try. First, I could move the field EulerTourNode.graph
|
||||
to EulerTourVertex, in order to save a little bit of space. Second, I could
|
||||
create EulerTourNode and EulerTourVertex subclasses specifically for the top
|
||||
level, and offload the fields related to user augmentation to those
|
||||
subclasses. These changes seem inelegant, but it might be worth checking the
|
||||
effect they have on performance.
|
||||
- I looked at the heuristics recommended in
|
||||
http://people.csail.mit.edu/karger/Papers/impconn.pdf (Iyer, et al. (2001): An
|
||||
Experimental Study of Poly-Logarithmic Fully-Dynamic Connectivity Algorithms).
|
||||
|
||||
@@ -363,11 +363,10 @@ public class ConnGraphTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a graph with a hub-and-spokes subgraph and a clique subgraph.
|
||||
* Tests the specified ConnGraph with a hub-and-spokes subgraph and a clique subgraph. The graph must be empty and
|
||||
* be augmented with SumAndMax objects.
|
||||
*/
|
||||
@Test
|
||||
public void testWheelAndClique() {
|
||||
ConnGraph graph = new ConnGraph(SumAndMax.AUGMENTATION);
|
||||
private void checkWheelAndClique(ConnGraph graph) {
|
||||
Random random = new Random(6170);
|
||||
ConnVertex hub = new ConnVertex(random);
|
||||
List<ConnVertex> spokes1 = new ArrayList<ConnVertex>(10);
|
||||
@@ -482,6 +481,25 @@ public class ConnGraphTest {
|
||||
assertNull(graph.getVertexAugmentation(spokes2.get(8)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a graph with a hub-and-spokes subgraph and a clique subgraph.
|
||||
*/
|
||||
@Test
|
||||
public void testWheelAndClique() {
|
||||
checkWheelAndClique(new ConnGraph(SumAndMax.AUGMENTATION));
|
||||
|
||||
ConnGraph graph1 = new ConnGraph(SumAndMax.MUTATING_AUGMENTATION);
|
||||
checkWheelAndClique(graph1);
|
||||
graph1.clear();
|
||||
checkWheelAndClique(graph1);
|
||||
|
||||
SumAndMaxPoolAndCache pool = new SumAndMaxPoolAndCache();
|
||||
ConnGraph graph2 = new ConnGraph(pool, pool);
|
||||
checkWheelAndClique(graph2);
|
||||
graph2.clear();
|
||||
checkWheelAndClique(graph2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the matching between vertices.get(columnIndex) and vertices.get(columnIndex + 1) to the permutation
|
||||
* suggested by newPermutation. See the comments for the implementation of testPermutations().
|
||||
@@ -599,11 +617,10 @@ public class ConnGraphTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a graph based on the United States.
|
||||
* Tests the specified ConnGraph with a graph based on the United States. The graph must be empty and be augmented
|
||||
* with SumAndMax objects.
|
||||
*/
|
||||
@Test
|
||||
public void testUnitedStates() {
|
||||
ConnGraph graph = new ConnGraph(SumAndMax.AUGMENTATION);
|
||||
private void checkUnitedStates(ConnGraph graph) {
|
||||
Random random = new Random(6170);
|
||||
ConnVertex alabama = new ConnVertex(random);
|
||||
assertNull(graph.setVertexAugmentation(alabama, new SumAndMax(7, 1819)));
|
||||
@@ -1009,6 +1026,15 @@ public class ConnGraphTest {
|
||||
assertNull(graph.getComponentAugmentation(arkansas));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a graph based on the United States.
|
||||
*/
|
||||
@Test
|
||||
public void testUnitedStates() {
|
||||
checkUnitedStates(new ConnGraph(SumAndMax.AUGMENTATION));
|
||||
checkUnitedStates(new ConnGraph(SumAndMax.MUTATING_AUGMENTATION));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ConnectivityGraph on the graph for a dodecahedron.
|
||||
*/
|
||||
|
||||
31
src/test/java/baritone/builder/connectivity/Reference.java
Normal file
31
src/test/java/baritone/builder/connectivity/Reference.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package baritone.builder.connectivity;
|
||||
|
||||
/**
|
||||
* Wraps a value using reference equality. In other words, two references are equal only if their values are the same
|
||||
* object instance, as in ==.
|
||||
*
|
||||
* @param <T> The type of value.
|
||||
*/
|
||||
class Reference<T> {
|
||||
/**
|
||||
* The value this wraps.
|
||||
*/
|
||||
private final T value;
|
||||
|
||||
public Reference(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof Reference)) {
|
||||
return false;
|
||||
}
|
||||
Reference<?> reference = (Reference<?>) obj;
|
||||
return value == reference.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(value);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
package baritone.builder.connectivity;
|
||||
|
||||
import baritone.builder.utils.com.github.btrekkie.connectivity.Augmentation;
|
||||
import baritone.builder.utils.com.github.btrekkie.connectivity.MutatingAugmentation;
|
||||
|
||||
/**
|
||||
* Stores two values: a sum and a maximum. Used for testing augmentation in ConnGraph.
|
||||
@@ -40,9 +41,35 @@ class SumAndMax {
|
||||
}
|
||||
};
|
||||
|
||||
public final int sum;
|
||||
/**
|
||||
* A MutatingAugmentation that combines two SumAndMaxes into one.
|
||||
*/
|
||||
public static final MutatingAugmentation MUTATING_AUGMENTATION = new MutatingAugmentation() {
|
||||
@Override
|
||||
public void combine(Object value1, Object value2, Object result) {
|
||||
SumAndMax sumAndMax1 = (SumAndMax) value1;
|
||||
SumAndMax sumAndMax2 = (SumAndMax) value2;
|
||||
SumAndMax sumAndMaxResult = (SumAndMax) result;
|
||||
sumAndMaxResult.sum = sumAndMax1.sum + sumAndMax2.sum;
|
||||
sumAndMaxResult.max = Math.max(sumAndMax1.max, sumAndMax2.max);
|
||||
}
|
||||
|
||||
public final int max;
|
||||
@Override
|
||||
public Object newAugmentation() {
|
||||
return new SumAndMax();
|
||||
}
|
||||
};
|
||||
|
||||
public int sum;
|
||||
|
||||
public int max;
|
||||
|
||||
/**
|
||||
* Constructs a new SumAndMax with an arbitrary sum and max.
|
||||
*/
|
||||
public SumAndMax() {
|
||||
|
||||
}
|
||||
|
||||
public SumAndMax(int sum, int max) {
|
||||
this.sum = sum;
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package baritone.builder.connectivity;
|
||||
|
||||
import baritone.builder.utils.com.github.btrekkie.connectivity.Augmentation;
|
||||
import baritone.builder.utils.com.github.btrekkie.connectivity.AugmentationReleaseListener;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An Augmentation implementation for SumAndMax that recycles (or "pools") SumAndMax instances and uses caching. This
|
||||
* should be passed to the ConnGraph constructor as an AugmentationReleaseListener so that it can recycle unused
|
||||
* SumAndMax objects.
|
||||
*/
|
||||
public class SumAndMaxPoolAndCache implements Augmentation, AugmentationReleaseListener {
|
||||
/**
|
||||
* The maximum number of unused SumAndMax objects to store in a pool.
|
||||
*/
|
||||
private static final int CAPACITY = 3;
|
||||
|
||||
/**
|
||||
* The maximum "sum" and "max" values to store in the cache. We cache all possible SumAndMax instances where "sum"
|
||||
* and "max" are both in the range [0, CACHE_SIZE).
|
||||
*/
|
||||
private static final int CACHE_SIZE = 10;
|
||||
|
||||
/**
|
||||
* A pool of SumAndMax instances we may reuse. The array has length CAPACITY, but only the first "size" elements
|
||||
* contain reusable SumAndMax objects. The values in the array after the first "size" are unspecified.
|
||||
*/
|
||||
private SumAndMax[] pool = new SumAndMax[CAPACITY];
|
||||
|
||||
/**
|
||||
* The number of reusable SumAndMax instances in the pool.
|
||||
*/
|
||||
private int size;
|
||||
|
||||
/**
|
||||
* A CACHE_SIZE x CACHE_SIZE array of cached SumAndMax instances. cache[m][s] is equal to "new SumAndMax(s, m)".
|
||||
*/
|
||||
private SumAndMax[][] cache;
|
||||
|
||||
/**
|
||||
* A map from each SumAndMax object that is a combined augmentation value that the graph has ownership of to the
|
||||
* number of ownership claims the graph has on the object.
|
||||
*/
|
||||
private Map<Reference<SumAndMax>, Integer> ownershipCounts = new HashMap<Reference<SumAndMax>, Integer>();
|
||||
|
||||
public SumAndMaxPoolAndCache() {
|
||||
cache = new SumAndMax[CACHE_SIZE][CACHE_SIZE];
|
||||
for (int max = 0; max < CACHE_SIZE; max++) {
|
||||
for (int sum = 0; sum < CACHE_SIZE; sum++) {
|
||||
cache[max][sum] = new SumAndMax(sum, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object combine(Object value1, Object value2) {
|
||||
SumAndMax sumAndMax1 = (SumAndMax) value1;
|
||||
SumAndMax sumAndMax2 = (SumAndMax) value2;
|
||||
int sum = sumAndMax1.sum + sumAndMax2.sum;
|
||||
int max = Math.max(sumAndMax1.max, sumAndMax2.max);
|
||||
|
||||
SumAndMax result;
|
||||
if (sum >= 0 && sum < CACHE_SIZE && max >= 0 && max < CACHE_SIZE) {
|
||||
result = cache[max][sum];
|
||||
} else if (size == 0) {
|
||||
result = new SumAndMax(sum, max);
|
||||
} else {
|
||||
size--;
|
||||
result = pool[size];
|
||||
result.sum = sum;
|
||||
result.max = max;
|
||||
}
|
||||
|
||||
Reference<SumAndMax> resultReference = new Reference<SumAndMax>(result);
|
||||
Integer count = ownershipCounts.get(resultReference);
|
||||
ownershipCounts.put(resultReference, count != null ? count + 1 : 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared implementation of combinedAugmentationReleased and vertexAugmentationReleased.
|
||||
*
|
||||
* @param obj The object that was released.
|
||||
* @param isCombined Whether this is for a call to combinedAugmentationReleased.
|
||||
*/
|
||||
private void augmentationReleased(SumAndMax obj, boolean isCombined) {
|
||||
if (obj == null) {
|
||||
throw new IllegalArgumentException("Cannot release a null value");
|
||||
}
|
||||
|
||||
if (isCombined) {
|
||||
Reference<SumAndMax> reference = new Reference<SumAndMax>(obj);
|
||||
Integer count = ownershipCounts.get(reference);
|
||||
if (count == null) {
|
||||
throw new RuntimeException(
|
||||
"ConnGraph attempted to release a combined augmentation object it did not own");
|
||||
}
|
||||
if (count <= 1) {
|
||||
ownershipCounts.remove(reference);
|
||||
} else {
|
||||
ownershipCounts.put(reference, count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.sum < 0 || obj.sum >= CACHE_SIZE || obj.max < 0 || obj.max >= CACHE_SIZE) {
|
||||
if (size < CAPACITY) {
|
||||
pool[size] = obj;
|
||||
size++;
|
||||
} else if (!isCombined) {
|
||||
// We want to test reusing vertex augmentation objects, so we add the object to the pool even if the
|
||||
// pool is already full
|
||||
pool[CAPACITY - 1] = obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void combinedAugmentationReleased(Object obj) {
|
||||
augmentationReleased((SumAndMax) obj, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void vertexAugmentationReleased(Object obj) {
|
||||
augmentationReleased((SumAndMax) obj, false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user