Skip to content

Commit dcf959e

Browse files
TBlueFHexay
andcommitted
Fix lowres changes getting lost when a tile is changed while it is saved, Closes #883
Co-authored-by: Hexay <97907668+Hexay@users.noreply.github.com>
1 parent 691a525 commit dcf959e

2 files changed

Lines changed: 46 additions & 25 deletions

File tree

‎core/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresLayer.java‎

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
import java.io.IOException;
3838
import java.io.InputStream;
39+
import java.util.HashMap;
3940
import java.util.List;
4041
import java.util.Map;
4142
import java.util.concurrent.ConcurrentHashMap;
@@ -99,10 +100,22 @@ public void removeTileUpdateListener(BiConsumer<Vector2i, Integer> listener) {
99100
}
100101

101102
public synchronized void save() {
102-
pendingChanges.entrySet().removeIf(entry -> saveTile(entry.getKey(), entry.getValue()));
103-
if (pendingChanges.size() >= DISCARD_THRESHOLD) {
104-
Logger.global.logDebug("Discarding changes of " + pendingChanges.size() + " lowres-tiles that failed to save!");
105-
pendingChanges.clear();
103+
Map<Vector2i, LowresTile> failed = new HashMap<>();
104+
105+
// save tiles and remember which tiles are not saved
106+
pendingChanges.forEach((pos, tile) -> {
107+
if (!saveTile(pos, tile)) failed.put(pos, tile);
108+
});
109+
110+
// if there is more than DISCARD_THRESHOLD failed saves, drop them
111+
if (failed.size() >= DISCARD_THRESHOLD) {
112+
Logger.global.logDebug("Discarding changes of " + failed.size() + " lowres-tiles that failed to save!");
113+
failed.forEach(pendingChanges::remove);
114+
}
115+
116+
// atomically test every tile if it is modified and remove it if it is not
117+
for (Vector2i pos : pendingChanges.keySet()) {
118+
pendingChanges.computeIfPresent(pos, (_, tile) -> tile.isModified() ? tile : null);
106119
}
107120
}
108121

@@ -124,6 +137,7 @@ private LowresTile createTile(Vector2i tilePos) {
124137
}
125138

126139
private boolean saveTile(Vector2i tilePos, LowresTile tile) {
140+
if (!tile.isModified()) return true;
127141

128142
// check if storage is closed
129143
if (storage.isClosed()){
@@ -134,7 +148,8 @@ private boolean saveTile(Vector2i tilePos, LowresTile tile) {
134148
// save the tile
135149
try {
136150
storage.write(tilePos.getX(), tilePos.getY(), tile::save);
137-
} catch (IOException e) {
151+
} catch (Exception e) {
152+
tile.markModified();
138153
Logger.global.logError("Failed to save tile " + tilePos + " (lod: " + lod + ")", e);
139154
return false;
140155
}
@@ -193,35 +208,31 @@ private boolean saveTile(Vector2i tilePos, LowresTile tile) {
193208
return true;
194209
}
195210

196-
private LowresTile accessTile(int x, int z) {
197-
Vector2i tilePos = VECTOR_2_I_CACHE.get(x, z);
198-
LowresTile tile = tileCache.get(tilePos);
199-
200-
if (pendingChanges.size() >= MAX_PENDING) save();
201-
pendingChanges.put(tilePos, tile);
202-
203-
return tile;
204-
}
205-
206211
void set(int cellX, int cellZ, int pixelX, int pixelZ, Color color, int height, int blockLight) {
207-
accessTile(cellX, cellZ)
208-
.set(pixelX, pixelZ, color, height, blockLight);
212+
setPixel(cellX, cellZ, pixelX, pixelZ, color, height, blockLight);
209213

210214
// for seamless edges
211215
if (pixelX == 0) {
212-
accessTile(cellX - 1, cellZ)
213-
.set(tileGrid.getGridSize().getX(), pixelZ, color, height, blockLight);
216+
setPixel(cellX - 1, cellZ, tileGrid.getGridSize().getX(), pixelZ, color, height, blockLight);
214217
}
215218

216219
if (pixelZ == 0) {
217-
accessTile(cellX, cellZ - 1)
218-
.set(pixelX, tileGrid.getGridSize().getY(), color, height, blockLight);
220+
setPixel(cellX, cellZ - 1, pixelX, tileGrid.getGridSize().getY(), color, height, blockLight);
219221
}
220222

221223
if (pixelX == 0 && pixelZ == 0) {
222-
accessTile(cellX - 1, cellZ - 1)
223-
.set(tileGrid.getGridSize().getX(), tileGrid.getGridSize().getY(), color, height, blockLight);
224+
setPixel(cellX - 1, cellZ - 1, tileGrid.getGridSize().getX(), tileGrid.getGridSize().getY(), color, height, blockLight);
224225
}
225226
}
226227

228+
private void setPixel(int cellX, int cellZ, int pixelX, int pixelZ, Color color, int height, int blockLight) {
229+
Vector2i tilePos = VECTOR_2_I_CACHE.get(cellX, cellZ);
230+
LowresTile tile = tileCache.get(tilePos);
231+
232+
tile.set(pixelX, pixelZ, color, height, blockLight);
233+
234+
pendingChanges.put(tilePos, tile);
235+
if (pendingChanges.size() >= MAX_PENDING) save();
236+
}
237+
227238
}

‎core/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresTile.java‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import com.flowpowered.math.vector.Vector2i;
2828
import de.bluecolored.bluemap.core.util.math.Color;
29+
import lombok.Getter;
2930

3031
import javax.imageio.ImageIO;
3132
import java.awt.image.BufferedImage;
@@ -36,21 +37,24 @@
3637

3738
public class LowresTile {
3839

39-
public static final int HEIGHT_UNDEFINED = Integer.MIN_VALUE;
40-
4140
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
4241

4342
private final BufferedImage texture;
4443
private final Vector2i size;
4544

45+
@Getter
46+
private volatile boolean modified;
47+
4648
public LowresTile(Vector2i tileSize) {
4749
this.size = tileSize.add(1, 1); // add 1 for seamless edges
4850
this.texture = new BufferedImage(this.size.getX(), this.size.getY() * 2, BufferedImage.TYPE_INT_ARGB);
51+
this.modified = false; // technically true, but no need to save empty tiles
4952
}
5053

5154
public LowresTile(Vector2i tileSize, InputStream in) throws IOException {
5255
this.size = tileSize.add(1, 1); // add 1 for seamless edges
5356
this.texture = ImageIO.read(in);
57+
this.modified = false;
5458

5559
if (this.texture == null) {
5660
throw new IOException("No registered ImageReader is able to read the image-stream");
@@ -64,6 +68,7 @@ public LowresTile(Vector2i tileSize, InputStream in) throws IOException {
6468
public void set(int x, int z, Color color, int height, int blockLight) {
6569
lock.readLock().lock();
6670
try {
71+
if (!modified) modified = true;
6772
texture.setRGB(x, z, color.straight().getInt());
6873
texture.setRGB(x, size.getY() + z,
6974
(height & 0x0000FFFF) |
@@ -75,6 +80,10 @@ public void set(int x, int z, Color color, int height, int blockLight) {
7580
}
7681
}
7782

83+
public void markModified() {
84+
modified = true;
85+
}
86+
7887
public Color getColor(int x, int z, Color target) {
7988
return target.set(texture.getRGB(x, z));
8089
}
@@ -94,6 +103,7 @@ public void save(OutputStream out) throws IOException {
94103
lock.writeLock().lock();
95104
try {
96105
ImageIO.write(texture, "png", out);
106+
modified = false;
97107
} finally {
98108
lock.writeLock().unlock();
99109
}

0 commit comments

Comments
 (0)