Solve most race issue around the move(), making world hole less likely

This commit is contained in:
tom lee
2022-02-02 15:55:16 +08:00
parent 35ab1ce47f
commit ca81ed1efe
3 changed files with 276 additions and 161 deletions
@@ -39,6 +39,8 @@ import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.MovabeGridRingList;
import com.seibel.lod.core.util.MovabeGridRingList.Pos;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
@@ -75,7 +77,7 @@ public class LodDimension
// these three variables are private to force use of the getWidth() method
// which is a safer way to get the width then directly asking the arrays
/** stores all the regions in this dimension */
public volatile LodRegion[][] regions;
public MovabeGridRingList<LodRegion> regions;
//NOTE: This list pos is relative to center
private volatile RegionPos[] iteratorList = null;
@@ -91,8 +93,6 @@ public class LodDimension
private LodDimensionFileHandler fileHandler;
private final RegionPos center;
public volatile int dirtiedRegionsRoughCount = 0;
private boolean isCutting = false;
@@ -143,9 +143,7 @@ public class LodDimension
}
regions = new LodRegion[width][width];
center = new RegionPos(0, 0);
regions = new MovabeGridRingList<LodRegion>(halfWidth, 0, 0);
generateIteratorList();
}
@@ -181,91 +179,8 @@ public class LodDimension
{
ClientApi.LOGGER.info("LodDim MOVE. Offset: "+regionOffset);
saveDirtyRegionsToFile(false); //async add dirty regions to be saved.
int xOffset = regionOffset.x;
int zOffset = regionOffset.z;
// if the x or z offset is equal to or greater than
// the total width, just delete the current data
// and update the centerX and/or centerZ
if (Math.abs(xOffset) >= width || Math.abs(zOffset) >= width)
{
for (int x = 0; x < width; x++)
for (int z = 0; z < width; z++) {
regions[x][z] = null;
}
// update the new center
center.x += xOffset;
center.z += zOffset;
return;
}
// X
if (xOffset > 0)
{
// move everything over to the left (as the center moves to the right)
for (int x = 0; x < width; x++)
{
for (int z = 0; z < width; z++)
{
if (x + xOffset < width)
regions[x][z] = regions[x + xOffset][z];
else
regions[x][z] = null;
}
}
}
else
{
// move everything over to the right (as the center moves to the left)
for (int x = width - 1; x >= 0; x--)
{
for (int z = 0; z < width; z++)
{
if (x + xOffset >= 0)
regions[x][z] = regions[x + xOffset][z];
else
regions[x][z] = null;
}
}
}
// Z
if (zOffset > 0)
{
// move everything up (as the center moves down)
for (int x = 0; x < width; x++)
{
for (int z = 0; z < width; z++)
{
if (z + zOffset < width)
regions[x][z] = regions[x][z + zOffset];
else
regions[x][z] = null;
}
}
}
else
{
// move everything down (as the center moves up)
for (int x = 0; x < width; x++)
{
for (int z = width - 1; z >= 0; z--)
{
if (z + zOffset >= 0)
regions[x][z] = regions[x][z + zOffset];
else
regions[x][z] = null;
}
}
}
// update the new center
center.x += xOffset;
center.z += zOffset;
Pos p = regions.getCenter();
regions.move(p.x+regionOffset.x, p.y+regionOffset.z);
ClientApi.LOGGER.info("LodDim MOVE complete. Offset: "+regionOffset);
}
@@ -280,19 +195,13 @@ public class LodDimension
{
int xRegion = LevelPosUtil.getRegion(detailLevel, levelPosX);
int zRegion = LevelPosUtil.getRegion(detailLevel, levelPosZ);
int xIndex = (xRegion - center.x) + halfWidth;
int zIndex = (zRegion - center.z) + halfWidth;
LodRegion region = regions.get(xRegion, zRegion);
if (!regionIsInRange(xRegion, zRegion))
return null;
// throw new ArrayIndexOutOfBoundsException("Region for level pos " + LevelPosUtil.toString(detailLevel, posX, posZ) + " out of range");
else if (regions[xIndex][zIndex] == null)
return null;
else if (regions[xIndex][zIndex].getMinDetailLevel() > detailLevel)
if (region != null && region.getMinDetailLevel() > detailLevel)
return null;
//throw new InvalidParameterException("Region for level pos " + LevelPosUtil.toString(detailLevel, posX, posZ) + " currently only reach level " + regions[xIndex][zIndex].getMinDetailLevel());
return regions[xIndex][zIndex];
return region;
}
/**
@@ -303,37 +212,29 @@ public class LodDimension
*/
public LodRegion getRegion(int regionPosX, int regionPosZ)
{
int xIndex = (regionPosX - center.x) + halfWidth;
int zIndex = (regionPosZ - center.z) + halfWidth;
if (!regionIsInRange(regionPosX, regionPosZ))
return null;
//throw new ArrayIndexOutOfBoundsException("Region " + regionPosX + " " + regionPosZ + " out of range");
return regions[xIndex][zIndex];
return regions.get(regionPosX, regionPosZ);
}
/** Useful when iterating over every region. */
@Deprecated
public LodRegion getRegionByArrayIndex(int xIndex, int zIndex)
{
return regions[xIndex][zIndex];
Pos p = regions.getMinInRange();
return regions.get(p.x+xIndex, p.y+zIndex);
}
/**
* Overwrite the LodRegion at the location of newRegion with newRegion.
* @throws ArrayIndexOutOfBoundsException if newRegion is outside what can be stored in this LodDimension.
*/
public synchronized void addOrOverwriteRegion(LodRegion newRegion) throws ArrayIndexOutOfBoundsException
/*public synchronized void addOrOverwriteRegion(LodRegion newRegion) throws ArrayIndexOutOfBoundsException
{
int xIndex = (newRegion.regionPosX - center.x) + halfWidth;
int zIndex = (newRegion.regionPosZ - center.z) + halfWidth;
if (!regionIsInRange(newRegion.regionPosX, newRegion.regionPosZ))
// out of range
throw new ArrayIndexOutOfBoundsException("Region " + newRegion.regionPosX + ", " + newRegion.regionPosZ + " out of range");
regions[xIndex][zIndex] = newRegion;
}
}*/
public interface PosComsumer {
void run(int x, int z);
}
@@ -342,7 +243,7 @@ public class LodDimension
int ox,oy,dx,dy;
ox = oy = dx = 0;
dy = -1;
int len = regions.length;
int len = regions.getSize();
int maxI = len*len;
int halfLen = len/2;
for(int i =0; i < maxI; i++){
@@ -384,20 +285,18 @@ public class LodDimension
Runnable thread = () -> {
//ClientApi.LOGGER.info("LodDim cut Region: " + playerPosX + "," + playerPosZ);
totalDirtiedRegions = 0;
Pos minPos = regions.getMinInRange();
// go over every region in the dimension
iterateWithSpiral((int x, int z) -> {
int regionX;
int regionZ;
int minDistance;
byte detail;
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
LodRegion region = regions[x][z];
LodRegion region = regions.get(x+minPos.x, z+minPos.y);
if (region != null && region.needSaving) totalDirtiedRegions++;
if (region != null && !region.needSaving && region.isWriting==0) {
// check what detail level this region should be
// and cut it if it is higher then that
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ,
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, x+minPos.x, z+minPos.y,
playerPosX, playerPosZ);
detail = DetailDistanceUtil.getDetailLevelFromDistance(minDistance);
if (region.getMinDetailLevel() < detail) {
@@ -435,6 +334,7 @@ public class LodDimension
Runnable thread = () -> {
//ClientApi.LOGGER.info("LodDim expend Region: " + playerPosX + "," + playerPosZ);
Pos minPos = regions.getMinInRange();
iterateWithSpiral((int x, int z) -> {
int regionX;
int regionZ;
@@ -443,10 +343,10 @@ public class LodDimension
int maxDistance;
byte minDetail;
byte maxDetail;
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
regionX = x + minPos.x;
regionZ = z + minPos.y;
final RegionPos regionPos = new RegionPos(regionX, regionZ);
region = regions[x][z];
region = regions.get(regionX, regionZ);
if (region != null && region.isWriting!=0) return; // FIXME: A crude attempt at lowering chance of race condition!
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX,
@@ -459,13 +359,13 @@ public class LodDimension
boolean updated = false;
if (region == null) {
region = getRegionFromFile(regionPos, minDetail, verticalQuality);
regions[x][z] = region;
regions.set(regionX, regionZ, region);
updated = true;
} else if (region.getVerticalQuality() != verticalQuality ||
region.getMinDetailLevel() > minDetail) {
// The 'getRegionFromFile' will flush and save the region if it returns a new one
region = getRegionFromFile(regions[x][z], minDetail, verticalQuality);
regions[x][z] = region;
region = getRegionFromFile(region, minDetail, verticalQuality);
regions.set(regionX, regionZ, region);
updated = true;
} else if (minDetail <= dropoffSwitch && region.lastMaxDetailLevel != maxDetail) {
region.lastMaxDetailLevel = maxDetail;
@@ -531,12 +431,12 @@ public class LodDimension
// This ensures that we don't spawn way too much regions without finish flushing them first.
if (dirtiedRegionsRoughCount > 16) return posToGenerate;
GenerationPriority allowedPriority = dirtiedRegionsRoughCount>12 ? GenerationPriority.NEAR_FIRST : priority;
Pos minPos = regions.getMinInRange();
iterateByDistance((int x, int z) -> {
boolean isCloseRange = (Math.abs(x-halfWidth)+Math.abs(z-halfWidth)<=2);
//boolean isCloseRange = true;
//All of this is handled directly by the region, which scan every pos from top to bottom of the quad tree
LodRegion lodRegion = regions[x][z];
LodRegion lodRegion = regions.get(minPos.x+x, minPos.y+z);
if (lodRegion != null && lodRegion.needRecheckGenPoint) {
@@ -724,22 +624,26 @@ public class LodDimension
*/
public boolean regionIsInRange(int regionX, int regionZ)
{
int xIndex = (regionX - center.x) + halfWidth;
int zIndex = (regionZ - center.z) + halfWidth;
return xIndex >= 0 && xIndex < width && zIndex >= 0 && zIndex < width;
return regions.inRange(regionX, regionZ);
}
/** Returns the dimension's center region position X value */
@Deprecated // Use getCenterRegionPos() instead
public int getCenterRegionPosX()
{
return center.x;
return regions.getCenter().x;
}
/** Returns the dimension's center region position Z value */
@Deprecated // Use getCenterRegionPos() instead
public int getCenterRegionPosZ()
{
return center.z;
return regions.getCenter().y;
}
public RegionPos getCenterRegionPos() {
Pos p = regions.getCenter();
return new RegionPos(p.x, p.y);
}
/** returns the width of the dimension in regions */
@@ -748,7 +652,7 @@ public class LodDimension
// we want to get the length directly from the
// source to make sure it is in sync with region
// and isRegionDirty
return regions != null ? regions.length : width;
return regions != null ? regions.getSize() : width;
}
/** Update the width of this dimension, in regions */
@@ -756,8 +660,8 @@ public class LodDimension
{
width = newWidth;
halfWidth = width/ 2;
regions = new LodRegion[width][width];
Pos p = regions.getCenter();
regions = new MovabeGridRingList<LodRegion>(halfWidth, p.x, p.y);
generateIteratorList();
}
@@ -767,18 +671,7 @@ public class LodDimension
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Dimension : \n");
for (LodRegion[] lodRegions : regions)
{
for (LodRegion region : lodRegions)
{
if (region == null)
stringBuilder.append("n");
else
stringBuilder.append(region.getMinDetailLevel());
stringBuilder.append("\t");
}
stringBuilder.append("\n");
}
stringBuilder.append(regions.toDetailString());
return stringBuilder.toString();
}
@@ -0,0 +1,232 @@
package com.seibel.lod.core.util;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
public class MovabeGridRingList<T> extends ArrayList<T> implements List<T> {
private static final long serialVersionUID = -7743190533384530134L;
public static class Pos {
public final int x;
public final int y;
Pos(int x, int y) {this.x=x; this.y=y;}
}
private AtomicReference<Pos> pos = new AtomicReference<Pos>();
private final int halfSize;
private final int size;
private final ReentrantReadWriteLock moveLock = new ReentrantReadWriteLock();
public MovabeGridRingList(int halfSize, int centerX, int centerY) {
super((halfSize * 2 + 1) * (halfSize * 2 + 1));
size = halfSize * 2 + 1;
this.halfSize = halfSize;
pos.set(new Pos(centerX-halfSize, centerY-halfSize));
clear();
}
@Override
public void clear() {
moveLock.writeLock().lock();
try {
super.clear();
super.ensureCapacity(size*size);
for (int i=0; i<size*size; i++) {
super.add(null);
}
} finally {
moveLock.writeLock().unlock();
}
}
public void clear(Consumer<? super T> d) {
moveLock.writeLock().lock();
try {
super.forEach((t) -> {
if (t!=null) d.accept(t);
});
super.clear();
super.ensureCapacity(size*size);
for (int i=0; i<size*size; i++) {
super.add(null);
}
} finally {
moveLock.writeLock().unlock();
}
}
public Pos getCenter() {
Pos bottom = pos.get();
return new Pos(bottom.x+halfSize, bottom.y+halfSize);
}
public Pos getMinInRange() {
return pos.get();
}
public Pos getMaxInRange() {
Pos bottom = pos.get();
return new Pos(bottom.x+size-1, bottom.y+size-1);
}
public int getSize() {return size;}
public int getHalfSize() {return halfSize;}
// WARNNING! Be careful with race condition!
// The grid may get moved after this query!
public boolean inRange(int x, int y) {
Pos min = pos.get();
return (x>=min.x && x<min.x+size && y>=min.y && y<min.y+size);
}
private boolean _inRangeAquired(int x, int y, Pos min) {
return (x>=min.x && x<min.x+size && y>=min.y && y<min.y+size);
}
private T _getUnsafe(int x, int y) {
return super.get(Math.floorMod(x, size) + Math.floorMod(y, size)*size);
}
private void _setUnsafe(int x, int y, T t) {
super.set(Math.floorMod(x, size) + Math.floorMod(y, size)*size, t);
}
private T _swapUnsafe(int x, int y, T t) {
return super.set(Math.floorMod(x, size) + Math.floorMod(y, size)*size, t);
}
// return null if x,y is outside of the grid
public T get(int x, int y) {
Pos min = pos.get();
if (!_inRangeAquired(x, y, min)) return null;
moveLock.readLock().lock();
try {
Pos newMin = pos.get();
// Use EXECT compare here
if (min!=newMin)
if (!_inRangeAquired(x, y, newMin)) return null;
return _getUnsafe(x, y);
} finally {
moveLock.readLock().unlock();
}
}
// return false if x,y is outside of the grid
public boolean set(int x, int y, T t) {
Pos min = pos.get();
if (!_inRangeAquired(x, y, min)) return false;
moveLock.readLock().lock();
try {
Pos newMin = pos.get();
// Use EXECT compare here
if (min!=newMin)
if (!_inRangeAquired(x, y, newMin)) return false;
_setUnsafe(x, y, t);
return true;
} finally {
moveLock.readLock().unlock();
}
}
// return input t if x,y is outside of the grid
public T swap(int x, int y, T t) {
Pos min = pos.get();
if (!_inRangeAquired(x, y, min)) return t;
moveLock.readLock().lock();
try {
Pos newMin = pos.get();
// Use EXECT compare here
if (min!=newMin)
if (!_inRangeAquired(x, y, newMin)) return t;
return _swapUnsafe(x, y, t);
} finally {
moveLock.readLock().unlock();
}
}
// TODO: Impl this
/*
// do a compare and set
public boolean compareAndSet(int x, int y, T expected, T toBeSet) {
Pos min = pos.get();
if (!_inRangeAquired(x, y, min)) return false;
moveLock.readLock().lock();
try {
Pos newMin = pos.get();
// Use EXECT compare here
if (min!=newMin)
if (!_inRangeAquired(x, y, newMin)) return false;
return _compareAndSetUnsafe(x, y, expected, toBeSet);
} finally {
moveLock.readLock().unlock();
}
}*/
// return null if x,y is outside of the grid
// Otherwise, return the new value (for chaining)
public T setChained(int x, int y, T t) {
return set(x,y,t) ? t : null;
}
// Return false if haven't changed. Return true if it did
public boolean move(int newCenterX, int newCenterY) {
return move(newCenterX, newCenterY, null);
}
public boolean move(int newCenterX, int newCenterY, Consumer<? super T> d) {
Pos cPos = pos.get();
int newMinX = newCenterX - halfSize;
int newMinY = newCenterY - halfSize;
if (cPos.x == newMinX && cPos.y == newMinY)
return false;
moveLock.writeLock().lock();
try {
cPos = pos.get();
int deltaX = newMinX - cPos.x;
int deltaY = newMinY - cPos.y;
if (deltaX == 0 && deltaY == 0)
return false;
// if the x or z offset is equal to or greater than
// the total width, just delete the current data
// and update the pos
if (Math.abs(deltaX) >= size || Math.abs(deltaY) >= size) {
clear();
} else {
for (int x = 0; x < size; x++) {
for (int y = 0; y < size; y++) {
if (x - deltaX < 0 || y - deltaY < 0 || x - deltaX >= size || y - deltaY >= size) {
T t = _swapUnsafe(x + cPos.x, y + cPos.y, null);
if (t != null && d != null)
d.accept(t);
}
}
}
}
pos.set(new Pos(newMinX, newMinY));
return true;
} finally {
moveLock.writeLock().unlock();
}
}
@Override
public String toString() {
Pos p = pos.get();
return "MovabeGridRingList[" + p.x+halfSize + "," + p.y+halfSize + "] " + size + "*" + size + "[" + size() + "]";
}
public String toDetailString() {
StringBuilder str = new StringBuilder("\n");
int i = 0;
str.append(this);
str.append("\n");
for (T t : this) {
str.append(t!=null ? t.toString() : "NULL");
str.append(", ");
i++;
if (i % size == 0) {
str.append("\n");
}
}
return str.toString();
}
}
@@ -14,16 +14,6 @@ public class MovableGridList<T> extends ArrayList<T> implements List<T> {
private static final long serialVersionUID = 5366261085254591277L;
public static class Pos {
public int x;
public int y;
public Pos(int xx, int yy) {
x = xx;
y = yy;
}
}
private int centerX;
private int centerY;