Compare commits

..

3 Commits

Author SHA1 Message Date
Leijurv
54e7473a6e use mutatingattachment 2023-04-15 13:52:02 -07:00
Leijurv
da1a5c121f Merge remote-tracking branch 'btrekkie/dynamic-connectivity/augmentation-pool' into builder-2 2023-04-15 13:43:04 -07:00
William Jacobs
1586a7c7a6 Added augmentation object pooling
This introduces MutatingAugmentation and AugmentationListener, which add support for recycling unused augmentation objects. This may improve performance by reducing the number of object allocations.
2023-04-15 09:31:10 -04:00
12 changed files with 589 additions and 65 deletions

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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) {
}
}

View File

@@ -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);
}

View File

@@ -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<>();

View File

@@ -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;
}
}
}

View File

@@ -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();
}

View File

@@ -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).

View File

@@ -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.
*/

View 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);
}
}

View File

@@ -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;

View File

@@ -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);
}
}