refactor MovableGridRingList

This commit is contained in:
James Seibel
2023-02-04 15:49:44 -06:00
parent 1adf6e0582
commit e3caab440a
2 changed files with 395 additions and 349 deletions
@@ -219,7 +219,7 @@ public class RenderBufferHandler {
MovableGridRingList<LodRenderSection> referenceList = target.getRingList(topDetail);
Pos2D center = referenceList.getCenter();
//boolean moved = renderBufferNodes.getCenterBlockPos().x != center.x || renderBufferNodes.getCenterBlockPos().y != center.y;
renderBufferNodes.move(center.x, center.y, RenderBufferNode::close); // Note: may lock the list
renderBufferNodes.moveTo(center.x, center.y, RenderBufferNode::close); // Note: may lock the list
@@ -32,13 +32,16 @@ import java.util.function.Consumer;
public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
{
/** the position of this grid closest to negative x/z infinity */
private final AtomicReference<Pos2D> minPosRef = new AtomicReference<>();
private final AtomicReference<Pos2D> pos = new AtomicReference<>();
private final int halfSize;
/** width of this grid list */
private final int size;
private final int halfSize;
private final ReentrantReadWriteLock moveLock = new ReentrantReadWriteLock();
/** used to iterate over each item in the list in an in-to-out order */
private Pos2D[] ringIteratorList = null;
@@ -54,17 +57,399 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
this.size = halfSize * 2 + 1;
this.halfSize = halfSize;
this.pos.set(new Pos2D(centerX-halfSize, centerY-halfSize));
this.minPosRef.set(new Pos2D(centerX-halfSize, centerY-halfSize));
this.clear();
}
//=====================//
// getters and setters //
//=====================//
/** see {@link MovableGridRingList#get(int, int)} for full documentation */
public T get(Pos2D pos) { return this.get(pos.x, pos.y); }
/** returns null if x,y is outside the grid */
public T get(int x, int y)
{
Pos2D min = this.minPosRef.get();
if (!this._inRangeAcquired(x, y, min))
{
return null;
}
this.moveLock.readLock().lock();
try
{
Pos2D newMin = this.minPosRef.get();
// Use EXACT compare here
if (min!=newMin)
{
if (!this._inRangeAcquired(x, y, newMin))
{
return null;
}
}
return this._getUnsafe(x, y);
}
finally
{
this.moveLock.readLock().unlock();
}
}
/** see {@link MovableGridRingList#set(int, int, T)} for full documentation */
public boolean set(Pos2D pos, T item) { return this.set(pos.x, pos.y, item); }
/** returns false if x,y is outside the grid */
public boolean set(int x, int y, T item)
{
Pos2D min = this.minPosRef.get();
if (!this._inRangeAcquired(x, y, min))
{
return false;
}
this.moveLock.readLock().lock();
try
{
Pos2D newMin = this.minPosRef.get();
// Use EXACT compare here
if (min!=newMin)
{
if (!this._inRangeAcquired(x, y, newMin))
{
return false;
}
}
this._setUnsafe(x, y, item);
return true;
}
finally
{
this.moveLock.readLock().unlock();
}
}
/** see {@link MovableGridRingList#setChained(int, int, T)} for full documentation */
public T setChained(Pos2D pos, T item) { return this.setChained(pos.x, pos.y, item); }
/**
* returns null if x,y is outside the grid
* Otherwise, returns the new value
*/
public T setChained(int x, int y, T item) { return this.set(x,y,item) ? item : null; }
//================//
// list modifiers //
//================//
/** see {@link MovableGridRingList#swap(int, int, T)} for full documentation */
public T swap(Pos2D pos, T item) { return this.swap(pos.x, pos.y, item); }
/** returns the input item if x,y is outside the grid */
public T swap(int x, int y, T item)
{
Pos2D min = this.minPosRef.get();
if (!this._inRangeAcquired(x, y, min))
{
return item;
}
this.moveLock.readLock().lock();
try
{
Pos2D newMin = this.minPosRef.get();
// Use EXACT compare here
if (min!=newMin)
{
if (!this._inRangeAcquired(x, y, newMin))
{
return item;
}
}
return this._swapUnsafe(x, y, item);
}
finally
{
this.moveLock.readLock().unlock();
}
}
/** see {@link MovableGridRingList#remove(int, int)} for full documentation */
public T remove(Pos2D pos) { return this.remove(pos.x, pos.y); }
/** remove and return the item at x,y; returns null if the x,y are outside the grid */
public T remove(int x, int y) { return this.swap(x, y, null); }
/** see {@link MovableGridRingList#clear(Consumer)} for full documentation */
@Override
public void clear() { this.clear(null); }
/** @param consumer the consumer run on each item before it is removed from the list */
public void clear(Consumer<? super T> consumer)
{
this.moveLock.writeLock().lock();
try
{
if (consumer != null)
{
super.forEach((item) ->
{
if (item != null)
{
consumer.accept(item);
}
});
}
super.clear();
super.ensureCapacity(this.size * this.size);
// TODO why are we filling the array will nulls? everything should already be null after the clear
for (int i = 0; i < this.size * this.size; i++)
{
super.add(null);
}
}
finally
{
this.moveLock.writeLock().unlock();
}
}
/** see {@link MovableGridRingList#moveTo(int, int, Consumer)} for full documentation */
public boolean moveTo(int newCenterX, int newCenterY) { return this.moveTo(newCenterX, newCenterY, null); }
/** Returns true if the grid was successfully moved, false otherwise */
public boolean moveTo(int newCenterX, int newCenterY, Consumer<? super T> consumer)
{
Pos2D cPos = this.minPosRef.get();
int newMinX = newCenterX - this.halfSize;
int newMinY = newCenterY - this.halfSize;
if (cPos.x == newMinX && cPos.y == newMinY)
{
return false;
}
this.moveLock.writeLock().lock();
try
{
cPos = this.minPosRef.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) >= this.size || Math.abs(deltaY) >= this.size)
{
this.clear(consumer);
}
else
{
for (int x = 0; x < this.size; x++)
{
for (int y = 0; y < this.size; y++)
{
if (x - deltaX < 0
|| y - deltaY < 0
|| x - deltaX >= this.size
|| y - deltaY >= this.size)
{
T item = this._swapUnsafe(x+cPos.x, y+cPos.y, null);
if (item != null && consumer != null)
{
consumer.accept(item);
}
}
}
}
}
this.minPosRef.set(new Pos2D(newMinX, newMinY));
return true;
}
finally
{
this.moveLock.writeLock().unlock();
}
}
//==================//
// position getters //
//==================//
public Pos2D getCenter() { return new Pos2D(this.minPosRef.get().x + this.halfSize, this.minPosRef.get().y + this.halfSize); }
public Pos2D getMinPosInRange() { return this.minPosRef.get(); }
public Pos2D getMaxPosInRange() { return new Pos2D(this.minPosRef.get().x + this.size-1, this.minPosRef.get().y + this.size-1); }
public int getSize() { return this.size; }
public int getHalfSize() { return this.halfSize; }
//================//
// helper methods //
//================//
/**
* Warning: Be careful with race conditions!
* The grid may move after this query!
*/
public boolean inRange(int x, int y)
{
Pos2D minPos = this.minPosRef.get();
return (x>=minPos.x
&& x<minPos.x+this.size
&& y>=minPos.y
&& y<minPos.y+this.size);
}
private boolean _inRangeAcquired(int x, int y, Pos2D min)
{
return (x>=min.x
&& x<min.x+this.size
&& y>=min.y
&& y<min.y+this.size);
}
private T _getUnsafe(int x, int y) { return super.get(Math.floorMod(x, this.size) + Math.floorMod(y, this.size)*this.size); }
private void _setUnsafe(int x, int y, T item) { super.set(Math.floorMod(x, this.size) + Math.floorMod(y, this.size)*this.size, item); }
private T _swapUnsafe(int x, int y, T item) { return super.set(Math.floorMod(x, this.size) + Math.floorMod(y, this.size)*this.size, item); }
// TODO: implement 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();
}
}*/
//===========//
// iterators //
//===========//
// TODO all iterators should either:
// A. treat nulls the same way, either passing them into the consumers to skipping them
// B. add the option to either skip or pass in nulls
/**
* TODO: Use MutablePos2D in the future
* Will pass in null entries
*/
public void forEachPos(BiConsumer<? super T, Pos2D> consumer)
{
this.moveLock.readLock().lock();
try
{
Pos2D min = this.minPosRef.get();
for (int x = min.x; x < min.x + this.size; x++)
{
for (int y = min.y; y < min.y + this.size; y++)
{
T t = this._getUnsafe(x, y);
consumer.accept(t, new Pos2D(x, y));
}
}
}
finally
{
this.moveLock.readLock().unlock();
}
}
/**
* TODO: Use MutablePos2D in the future
* Will skip null entries
*/
public void forEachOrdered(Consumer<? super T> consumer)
{
// create the iterator if necessary
if (this.ringIteratorList == null)
{
this.createRingIteratorList();
}
this.moveLock.readLock().lock();
try
{
Pos2D min = this.minPosRef.get();
for (Pos2D offset : this.ringIteratorList)
{
T item = this._getUnsafe(min.x + offset.x, min.y + offset.y);
if (item != null)
{
consumer.accept(item);
}
}
}
finally
{
this.moveLock.readLock().unlock();
}
}
/**
* TODO: Use MutablePos2D in the future
* Will pass in null entries
*/
public void forEachPosOrdered(BiConsumer<? super T, Pos2D> consumer)
{
// create the iterator if necessary
if (this.ringIteratorList == null)
{
this.createRingIteratorList();
}
this.moveLock.readLock().lock();
try
{
Pos2D min = this.minPosRef.get();
for (Pos2D offset : this.ringIteratorList)
{
LodUtil.assertTrue(this._inRangeAcquired(min.x + offset.x, min.y + offset.y, min));
T t = this._getUnsafe(min.x + offset.x, min.y + offset.y);
consumer.accept(t, new Pos2D(min.x + offset.x, min.y + offset.y));
}
}
finally
{
this.moveLock.readLock().unlock();
}
}
/**
* TODO: Check if this needs to be synchronized
* <br>
* FIXME: Make all usage of this class do stuff relative to the minPos instead of the center
*/
private void buildRingIteratorList()
private void createRingIteratorList()
{
this.ringIteratorList = null;
Pos2D[] posArray = new Pos2D[this.size*this.size];
@@ -103,353 +488,14 @@ public class MovableGridRingList<T> extends ArrayList<T> implements List<T>
//==============//
// base methods //
//==============//
@Override
public void clear() { this.clear(null); }
/** @param consumer the consumer run on each item before it is removed from the list */
public void clear(Consumer<? super T> consumer)
{
this.moveLock.writeLock().lock();
try
{
if (consumer != null)
{
super.forEach((t) ->
{
if (t != null)
{
consumer.accept(t);
}
});
}
super.clear();
super.ensureCapacity(this.size * this.size);
// TODO why are we filling the array will nulls? everything should already be null after the clear
for (int i = 0; i < this.size * this.size; i++)
{
super.add(null);
}
}
finally
{
this.moveLock.writeLock().unlock();
}
}
public Pos2D getCenter()
{
Pos2D bottom = this.pos.get();
return new Pos2D(bottom.x + this.halfSize, bottom.y + this.halfSize);
}
public Pos2D getMinInRange() { return this.pos.get(); }
public Pos2D getMaxInRange() {
Pos2D bottom = this.pos.get();
return new Pos2D(bottom.x + this.size-1, bottom.y + this.size-1);
}
public int getSize() { return this.size; }
public int getHalfSize() { return this.halfSize; }
/**
* Warning: Be careful with race conditions!
* The grid may move after this query!
*/
public boolean inRange(int x, int y)
{
Pos2D minPos = this.pos.get();
return (x>=minPos.x
&& x<minPos.x+this.size
&& y>=minPos.y
&& y<minPos.y+this.size);
}
private boolean _inRangeAquired(int x, int y, Pos2D min)
{
return (x>=min.x
&& x<min.x+this.size
&& y>=min.y
&& y<min.y+this.size);
}
private T _getUnsafe(int x, int y)
{
return super.get(Math.floorMod(x, this.size) + Math.floorMod(y, this.size)*this.size);
}
private void _setUnsafe(int x, int y, T t)
{
super.set(Math.floorMod(x, this.size) + Math.floorMod(y, this.size)*this.size, t);
}
private T _swapUnsafe(int x, int y, T t)
{
return super.set(Math.floorMod(x, this.size) + Math.floorMod(y, this.size)*this.size, t);
}
/** returns null if x,y is outside the grid */
public T get(int x, int y)
{
Pos2D min = pos.get();
if (!this._inRangeAquired(x, y, min))
{
return null;
}
this.moveLock.readLock().lock();
try
{
Pos2D newMin = pos.get();
// Use EXECT compare here
if (min!=newMin)
{
if (!_inRangeAquired(x, y, newMin))
{
return null;
}
}
return this._getUnsafe(x, y);
}
finally
{
this.moveLock.readLock().unlock();
}
}
/** returns false if x,y is outside the grid */
public boolean set(int x, int y, T t)
{
Pos2D min = this.pos.get();
if (!this._inRangeAquired(x, y, min))
{
return false;
}
this.moveLock.readLock().lock();
try
{
Pos2D newMin = this.pos.get();
// Use EXACT compare here
if (min!=newMin)
{
if (!this._inRangeAquired(x, y, newMin))
{
return false;
}
}
this._setUnsafe(x, y, t);
return true;
}
finally
{
this.moveLock.readLock().unlock();
}
}
// return input t if x,y is outside of the grid
public T swap(int x, int y, T t)
{
Pos2D min = this.pos.get();
if (!this._inRangeAquired(x, y, min))
{
return t;
}
this.moveLock.readLock().lock();
try
{
Pos2D newMin = this.pos.get();
// Use EXACT compare here
if (min!=newMin)
{
if (!this._inRangeAquired(x, y, newMin))
{
return t;
}
}
return this._swapUnsafe(x, y, t);
}
finally
{
this.moveLock.readLock().unlock();
}
}
public T remove(int x, int y) { return this.swap(x, y, null); }
public T get(Pos2D p) { return this.get(p.x, p.y); }
public boolean set(Pos2D p, T t) { return this.set(p.x, p.y, t); }
public T swap(Pos2D p, T t) { return this.swap(p.x, p.y, t); }
public T remove(Pos2D p) { return this.remove(p.x, p.y); }
// TODO: implement 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 this.set(x,y,t) ? t : null; }
public T setChained(Pos2D p, T t) { return this.setChained(p.x, p.y, t); }
/** Returns true if the grid was successfully moved, false otherwise */
public boolean move(int newCenterX, int newCenterY) { return this.move(newCenterX, newCenterY, null); }
public boolean move(int newCenterX, int newCenterY, Consumer<? super T> consumer)
{
Pos2D cPos = this.pos.get();
int newMinX = newCenterX - this.halfSize;
int newMinY = newCenterY - this.halfSize;
if (cPos.x == newMinX && cPos.y == newMinY)
{
return false;
}
this.moveLock.writeLock().lock();
try
{
cPos = this.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) >= this.size || Math.abs(deltaY) >= this.size)
{
this.clear(consumer);
}
else
{
for (int x = 0; x < this.size; x++)
{
for (int y = 0; y < this.size; y++)
{
if (x - deltaX < 0 || y - deltaY < 0 || x - deltaX >= this.size || y - deltaY >= this.size)
{
T t = this._swapUnsafe(x + cPos.x, y + cPos.y, null);
if (t != null && consumer != null)
{
consumer.accept(t);
}
}
}
}
}
this.pos.set(new Pos2D(newMinX, newMinY));
return true;
}
finally
{
this.moveLock.writeLock().unlock();
}
}
/**
* TODO: Use MutablePos2D in the future
* Will pass in null entries
*/
public void forEachPos(BiConsumer<? super T, Pos2D> consumer)
{
this.moveLock.readLock().lock();
try
{
Pos2D min = this.pos.get();
for (int x = min.x; x < min.x + this.size; x++)
{
for (int y = min.y; y < min.y + this.size; y++)
{
T t = this._getUnsafe(x, y);
consumer.accept(t, new Pos2D(x, y));
}
}
}
finally
{
this.moveLock.readLock().unlock();
}
}
/**
* TODO: Use MutablePos2D in the future
* Will skip null entries
*/
public void forEachOrdered(Consumer<? super T> consumer)
{
if (this.ringIteratorList == null)
{
this.buildRingIteratorList();
}
this.moveLock.readLock().lock();
try
{
Pos2D min = this.pos.get();
for (Pos2D offset : this.ringIteratorList)
{
T t = this._getUnsafe(min.x + offset.x, min.y + offset.y);
if (t != null)
{
consumer.accept(t);
}
}
}
finally
{
this.moveLock.readLock().unlock();
}
}
/**
* TODO: Use MutablePos2D in the future
* Will pass in null entries
*/
public void forEachPosOrdered(BiConsumer<? super T, Pos2D> consumer)
{
if (this.ringIteratorList == null)
{
this.buildRingIteratorList();
}
this.moveLock.readLock().lock();
try
{
Pos2D min = this.pos.get();
for (Pos2D offset : this.ringIteratorList)
{
LodUtil.assertTrue(this._inRangeAquired(min.x + offset.x, min.y + offset.y, min));
T t = this._getUnsafe(min.x + offset.x, min.y + offset.y);
consumer.accept(t, new Pos2D(min.x + offset.x, min.y + offset.y));
}
}
finally
{
this.moveLock.readLock().unlock();
}
}
@Override
public String toString()
{
Pos2D p = this.pos.get();
Pos2D p = this.minPosRef.get();
return this.getClass().getSimpleName() + "[" + (p.x+this.halfSize) + "," + (p.y+this.halfSize) + "] " + this.size + "*" + this.size + "[" + this.size() + "]";
}