Update #47 - Singleplayer lag fixes

This commit is contained in:
lax1dude
2025-01-19 13:26:27 -08:00
parent 3f5ee57068
commit 1f0d593a8c
2052 changed files with 133581 additions and 2339 deletions

View File

@ -0,0 +1,414 @@
/*
* HPPC
*
* Copyright (C) 2010-2024 Carrot Search s.c. and contributors
* All rights reserved.
*
* Refer to the full license file "LICENSE.txt":
* https://github.com/carrotsearch/hppc/blob/master/LICENSE.txt
*/
package com.carrotsearch.hppc;
import java.util.Arrays;
/**
* Optimal Piecewise Linear Approximation Model for <code>KType</code> keys.
*
* <p>Learns a mapping that returns a position for a <code>KType</code> key which is at most epsilon
* away from the correct one in a sorted list of keys. It is optimal and piecewise because it learns
* the minimum number of epsilon-approximate segments.
*
* <p>The PLA-model consists of a sequence segments. A segment s is a triple (key,slope,intercept)
* that indexes a range of keys through the function fs(k) = k × slope + intercept, which provides
* an epsilon-approximation of the position of the key k.
*/
public class PlaModel implements Accountable {
/** Initial capacity of the lower and upper point lists. */
private static final int INITIAL_CAPACITY = 1 << 8;
/** Epsilon precision of the PLA-model. */
private int epsilon;
/** First key of the current segment. */
private double firstKey;
/** Previous key used to check that keys are added in strictly increasing sequence. */
private double previousKey;
/** Number of points in the convex hull for the current segment. */
private int numPointsInHull;
/** Enclosing rectangle for the current segment. */
private final Point[] rect = new Point[4];
/**
* Ordered list of lower points for the current segment. Inside the list, allocated points are
* re-used.
*/
private final PointList lower = new PointList(INITIAL_CAPACITY);
/**
* Ordered list of upper points for the current segment. Inside the list, allocated points are
* re-used.
*/
private final PointList upper = new PointList(INITIAL_CAPACITY);
/** Index of the first lower point to compare to. */
private int lowerStart;
/** Index of the first upper point to compare to. */
private int upperStart;
// Re-used mutable points and slopes.
private final Point point1 = new Point();
private final Point point2 = new Point();
private final Slope slope1 = new Slope();
private final Slope slope2 = new Slope();
private final Slope slopeTmp = new Slope();
private final Slope slopeMin = new Slope();
private final Slope slopeMax = new Slope();
/**
* Creates an optimal PLA-model with the provided epsilon precision.
*
* @param epsilon must be greater than or equal to 0.
*/
public PlaModel(int epsilon) {
setEpsilon(epsilon);
for (int i = 0; i < rect.length; i++) {
rect[i] = new Point();
}
reset();
}
/** Sets epsilon precision which must be greater than or equal to 0. */
public void setEpsilon(int epsilon) {
if (epsilon < 0) {
throw new IllegalArgumentException("epsilon must be >= 0");
}
this.epsilon = epsilon;
}
private void reset() {
previousKey = Double.NEGATIVE_INFINITY;
numPointsInHull = 0;
lower.clear();
upper.clear();
}
/**
* Adds a key to this PLA-model. The keys must be provided in a strictly increasing sequence. That
* is, the key must be greater than the previous key.
*
* @param index The index of the key in the sorted key list.
* @param segmentConsumer The consumer to call when a new segment is built in the PLA-model.
*/
public void addKey(double key, int index, SegmentConsumer segmentConsumer) {
if (key <= previousKey) {
throw new IllegalArgumentException("Keys must be increasing");
}
previousKey = key;
point1.set(key, addEpsilon(index));
point2.set(key, subtractEpsilon(index));
if (numPointsInHull > 1) {
slope1.set(rect[0], rect[2]);
slope2.set(rect[1], rect[3]);
boolean outside_line1 = slopeTmp.set(rect[2], point1).isLessThan(slope1);
boolean outside_line2 = slopeTmp.set(rect[3], point2).isGreaterThan(slope2);
if (outside_line1 || outside_line2) {
produceSegment(segmentConsumer);
numPointsInHull = 0;
}
}
if (numPointsInHull == 0) {
firstKey = key;
rect[0].set(point1);
rect[1].set(point2);
upper.clear();
lower.clear();
upper.add(point1);
lower.add(point2);
upperStart = lowerStart = 0;
numPointsInHull++;
return;
}
if (numPointsInHull == 1) {
rect[2].set(point2);
rect[3].set(point1);
upper.add(point1);
lower.add(point2);
numPointsInHull++;
return;
}
if (slopeTmp.set(rect[1], point1).isLessThan(slope2)) {
// Find extreme slope.
slopeMin.set(point1, lower.get(lowerStart));
int min_i = lowerStart;
for (int i = lowerStart + 1; i < lower.size(); i++) {
slopeTmp.set(point1, lower.get(i));
if (slopeTmp.isGreaterThan(slopeMin)) {
break;
}
slopeMin.set(slopeTmp);
min_i = i;
}
rect[1].set(lower.get(min_i));
rect[3].set(point1);
lowerStart = min_i;
// Hull update.
int end = upper.size();
while (end >= upperStart + 2 && cross(upper.get(end - 2), upper.get(end - 1), point1) <= 0) {
end--;
}
upper.clearFrom(end);
upper.add(point1);
}
if (slopeTmp.set(rect[0], point2).isGreaterThan(slope1)) {
// Find extreme slope.
slopeMax.set(point2, upper.get(upperStart));
int max_i = upperStart;
for (int i = upperStart + 1; i < upper.size(); i++) {
slopeTmp.set(point2, upper.get(i));
if (slopeTmp.isLessThan(slopeMax)) {
break;
}
slopeMax.set(slopeTmp);
max_i = i;
}
rect[0].set(upper.get(max_i));
rect[2].set(point2);
upperStart = max_i;
// Hull update.
int end = lower.size();
while (end >= lowerStart + 2 && cross(lower.get(end - 2), lower.get(end - 1), point2) >= 0) {
end--;
}
lower.clearFrom(end);
lower.add(point2);
}
numPointsInHull++;
}
private void produceSegment(SegmentConsumer segmentConsumer) {
double slope;
long intercept;
if (numPointsInHull == 1) {
slope = 0d;
intercept = ((long) rect[0].y + rect[1].y) >>> 1;
} else {
Point p0 = rect[0];
Point p1 = rect[1];
Point p2 = rect[2];
Point p3 = rect[3];
// Compute the slope intersection point.
double intersectX;
double intersectY;
slope1.set(p0, p2);
slope2.set(p1, p3);
if (slope1.isEqual(slope2)) {
intersectX = p0.x;
intersectY = p0.y;
} else {
slopeTmp.set(p0, p1);
double a = slope1.dx * slope2.dy - slope1.dy * slope2.dx;
double b = (slopeTmp.dx * slope2.dy - slopeTmp.dy * slope2.dx) / a;
intersectX = p0.x + b * slope1.dx;
intersectY = p0.y + b * slope1.dy;
}
// Compute the slope range.
double minSlope = Slope.asDouble(p0, p2);
double maxSlope = Slope.asDouble(p1, p3);
// Compute the segment slope and intercept.
slope = (minSlope + maxSlope) / 2d;
intercept = (long) (intersectY - (intersectX - firstKey) * slope);
}
segmentConsumer.accept(firstKey, slope, intercept);
}
/**
* Finishes the PLA-model construction. Declares that no additional keys will be added. Builds the
* last segment and calls the provided {@link SegmentConsumer}.
*/
public void finish(SegmentConsumer segmentConsumer) {
produceSegment(segmentConsumer);
reset();
}
@Override
public long ramBytesAllocated() {
// int: epsilon, numPointsInHull, lowerStart, upperStart
// double: firstKey, previousKey
// Point: rect[4], point1, point2
// Slope: slope1, slope2, slopeTmp, slopeMin, slopeMax
return RamUsageEstimator.NUM_BYTES_OBJECT_HEADER
+ 4 * Integer.BYTES
+ 2 * Double.BYTES
+ 6L * Point.RAM_BYTES_ALLOCATED
+ lower.ramBytesAllocated()
+ upper.ramBytesAllocated()
+ 5L * Slope.RAM_BYTES_ALLOCATED;
}
@Override
public long ramBytesUsed() {
return ramBytesAllocated();
}
private int addEpsilon(int index) {
try {
return Math.addExact(index, epsilon);
} catch (ArithmeticException e) {
return Integer.MAX_VALUE;
}
}
private int subtractEpsilon(int index) {
try {
return Math.subtractExact(index, epsilon);
} catch (ArithmeticException e) {
return Integer.MIN_VALUE;
}
}
private static double cross(Point o, Point a, Point b) {
return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
}
/** Consumer notified when a new segment is built by the {@link PlaModel}. */
public interface SegmentConsumer {
/**
* Consumes a new segment. The segment is defined by the epsilon-approximation function fs(k) =
* k × slope + intercept.
*
* @param firstKey The first key of the segment.
* @param slope The segment slope.
* @param intercept The segment intercept.
*/
void accept(double firstKey, double slope, long intercept);
}
/** Re-usable mutable (x,y) point. */
private static class Point {
static final int RAM_BYTES_ALLOCATED =
RamUsageEstimator.NUM_BYTES_OBJECT_HEADER + Double.BYTES + Long.BYTES;
double x;
long y;
Point set(double x, long y) {
this.x = x;
this.y = y;
return this;
}
Point set(Point p) {
return set(p.x, p.y);
}
}
/** List of mutable {@link Point}. Re-uses allocated points instead of creating new instances. */
private static class PointList implements Accountable {
Point[] points;
int size;
int numAllocated;
PointList(int initialCapacity) {
points = new Point[initialCapacity];
}
void add(Point point) {
if (size == points.length) {
int newSize =
BoundedProportionalArraySizingStrategy.DEFAULT_INSTANCE.grow(points.length, size, 1);
points = Arrays.copyOf(points, newSize);
}
if (size == numAllocated) {
points[numAllocated++] = new Point();
}
points[size++].set(point);
}
Point get(int index) {
return points[index];
}
int size() {
return size;
}
void clear() {
size = 0;
}
void clearFrom(int end) {
size = end;
}
@Override
public long ramBytesAllocated() {
// int: size, numAllocated
return RamUsageEstimator.NUM_BYTES_OBJECT_HEADER
+ 2 * Integer.BYTES
+ RamUsageEstimator.shallowSizeOfArray(points)
+ (long) numAllocated * Point.RAM_BYTES_ALLOCATED;
}
@Override
public long ramBytesUsed() {
return ramBytesAllocated();
}
}
/** Re-usable mutable (dx,dy) slope. */
private static class Slope {
static final int RAM_BYTES_ALLOCATED =
RamUsageEstimator.NUM_BYTES_OBJECT_HEADER + Double.BYTES + Long.BYTES;
double dx;
long dy;
void set(Slope s) {
dx = s.dx;
dy = s.dy;
}
Slope set(Point p1, Point p2) {
dx = p2.x - p1.x;
dy = p2.y - p1.y;
return this;
}
boolean isLessThan(Slope s) {
return dy * s.dx < dx * s.dy;
}
boolean isGreaterThan(Slope s) {
return dy * s.dx > dx * s.dy;
}
boolean isEqual(Slope s) {
return Double.doubleToLongBits(dy * s.dx) == Double.doubleToLongBits(dx * s.dy);
}
static double asDouble(Point p1, Point p2) {
return (double) (p2.y - p1.y) / (p2.x - p1.x);
}
}
}