From e3caab440acc09aa758deff16b59e110c846dbd9 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 4 Feb 2023 15:49:44 -0600 Subject: [PATCH] refactor MovableGridRingList --- .../lod/core/render/RenderBufferHandler.java | 2 +- .../util/gridList/MovableGridRingList.java | 742 ++++++++++-------- 2 files changed, 395 insertions(+), 349 deletions(-) diff --git a/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java b/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java index 0f78ec526..cf87d64a0 100644 --- a/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java +++ b/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java @@ -219,7 +219,7 @@ public class RenderBufferHandler { MovableGridRingList 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 diff --git a/core/src/main/java/com/seibel/lod/core/util/gridList/MovableGridRingList.java b/core/src/main/java/com/seibel/lod/core/util/gridList/MovableGridRingList.java index 29b82e5e7..3d603b356 100644 --- a/core/src/main/java/com/seibel/lod/core/util/gridList/MovableGridRingList.java +++ b/core/src/main/java/com/seibel/lod/core/util/gridList/MovableGridRingList.java @@ -32,13 +32,16 @@ import java.util.function.Consumer; public class MovableGridRingList extends ArrayList implements List { + /** the position of this grid closest to negative x/z infinity */ + private final AtomicReference minPosRef = new AtomicReference<>(); - private final AtomicReference 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 extends ArrayList implements List 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 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 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.y + && y=min.x + && x=min.y + && y 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 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 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 *
* 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 extends ArrayList implements List + //==============// + // 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 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.y - && y=min.x - && x=min.y - && y 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 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 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 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() + "]"; }