2727import com .flowpowered .math .vector .Vector2i ;
2828import com .github .benmanes .caffeine .cache .Caffeine ;
2929import com .github .benmanes .caffeine .cache .LoadingCache ;
30- import com .github .benmanes .caffeine .cache .RemovalCause ;
31- import com .github .benmanes .caffeine .cache .Scheduler ;
3230import de .bluecolored .bluemap .core .BlueMap ;
3331import de .bluecolored .bluemap .core .logger .Logger ;
3432import de .bluecolored .bluemap .core .storage .GridStorage ;
4038import java .io .IOException ;
4139import java .io .InputStream ;
4240import java .io .OutputStream ;
41+ import java .util .Map ;
42+ import java .util .concurrent .ConcurrentHashMap ;
4343import java .util .concurrent .TimeUnit ;
4444
4545public class LowresLayer {
4646
47+ private static final int MAX_PENDING = 200 ;
48+ private static final int DISCARD_THRESHOLD = MAX_PENDING / 2 ;
49+
4750 private static final Vector2iCache VECTOR_2_I_CACHE = new Vector2iCache ();
4851
4952 private final GridStorage storage ;
@@ -52,9 +55,12 @@ public class LowresLayer {
5255 private final int lodFactor ;
5356
5457 private final int lod ;
58+ private final LoadingCache <Vector2i , LowresTile > tileWeakInstanceCache ;
5559 private final LoadingCache <Vector2i , LowresTile > tileCache ;
5660 @ Nullable private final LowresLayer nextLayer ;
5761
62+ private final Map <Vector2i , LowresTile > pendingChanges ;
63+
5864 public LowresLayer (
5965 GridStorage storage , Grid tileGrid , int lodFactor ,
6066 int lod , @ Nullable LowresLayer nextLayer
@@ -69,23 +75,33 @@ public LowresLayer(
6975
7076 // this extra cache makes sure that a tile instance is reused as long as it is still referenced somewhere ..
7177 // so always only one instance of the same lowres-tile exists
72- LoadingCache < Vector2i , LowresTile > tileWeakInstanceCache = Caffeine .newBuilder ()
78+ this . tileWeakInstanceCache = Caffeine .newBuilder ()
7379 .executor (BlueMap .THREAD_POOL )
7480 .weakValues ()
7581 .build (this ::createTile );
7682
7783 this .tileCache = Caffeine .newBuilder ()
7884 .executor (BlueMap .THREAD_POOL )
79- .scheduler (Scheduler .systemScheduler ())
80- .expireAfterAccess (10 , TimeUnit .SECONDS )
81- .expireAfterWrite (5 , TimeUnit .MINUTES )
82- .removalListener ((Vector2i key , LowresTile value , RemovalCause cause ) -> saveTile (key , value ))
85+ .softValues ()
86+ .maximumSize (1000 )
87+ .expireAfterAccess (1 , TimeUnit .MINUTES )
8388 .build (tileWeakInstanceCache ::get );
89+
90+ this .pendingChanges = new ConcurrentHashMap <>();
8491 }
8592
8693 public void save () {
94+ pendingChanges .entrySet ().removeIf (entry -> saveTile (entry .getKey (), entry .getValue ()));
95+ if (pendingChanges .size () >= DISCARD_THRESHOLD ) {
96+ Logger .global .logDebug ("Discarding changes of " + pendingChanges .size () + " lowres-tiles that failed to save!" );
97+ pendingChanges .clear ();
98+ }
99+ }
100+
101+ public void discard () {
102+ pendingChanges .clear ();
87103 tileCache .invalidateAll ();
88- tileCache . cleanUp ();
104+ tileWeakInstanceCache . invalidateAll ();
89105 }
90106
91107 private LowresTile createTile (Vector2i tilePos ) {
@@ -99,25 +115,25 @@ private LowresTile createTile(Vector2i tilePos) {
99115 return new LowresTile (tileGrid .getGridSize ());
100116 }
101117
102- private void saveTile (Vector2i tilePos , @ Nullable LowresTile tile ) {
103- if (tile == null ) return ;
118+ private boolean saveTile (Vector2i tilePos , LowresTile tile ) {
104119
105120 // check if storage is closed
106121 if (storage .isClosed ()){
107122 Logger .global .logDebug ("Tried to save tile " + tilePos + " (lod: " + lod + ") but storage is already closed." );
108- return ;
123+ return false ;
109124 }
110125
111126 // save the tile
112127 try (OutputStream out = storage .write (tilePos .getX (), tilePos .getY ())) {
113128 tile .save (out );
114129 } catch (IOException e ) {
115130 Logger .global .logError ("Failed to save tile " + tilePos + " (lod: " + lod + ")" , e );
131+ return false ;
116132 }
117133
118- // write to next LOD (prepare for the most confusing grid-math you will ever see)
119- if (this .nextLayer == null ) return ;
134+ if (this .nextLayer == null ) return true ;
120135
136+ // write to next LOD (prepare for the most confusing grid-math you will ever see)
121137 Color averageColor = new Color ();
122138 int averageHeight , averageBlockLight ;
123139 int count ;
@@ -160,29 +176,37 @@ private void saveTile(Vector2i tilePos, @Nullable LowresTile tile) {
160176 );
161177 }
162178 }
179+
180+ return true ;
163181 }
164182
165- private LowresTile getTile (int x , int z ) {
166- return tileCache .get (VECTOR_2_I_CACHE .get (x , z ));
183+ private LowresTile accessTile (int x , int z ) {
184+ Vector2i tilePos = VECTOR_2_I_CACHE .get (x , z );
185+ LowresTile tile = tileCache .get (tilePos );
186+
187+ if (pendingChanges .size () >= MAX_PENDING ) save ();
188+ pendingChanges .put (tilePos , tile );
189+
190+ return tile ;
167191 }
168192
169193 void set (int cellX , int cellZ , int pixelX , int pixelZ , Color color , int height , int blockLight ) {
170- getTile (cellX , cellZ )
194+ accessTile (cellX , cellZ )
171195 .set (pixelX , pixelZ , color , height , blockLight );
172196
173197 // for seamless edges
174198 if (pixelX == 0 ) {
175- getTile (cellX - 1 , cellZ )
199+ accessTile (cellX - 1 , cellZ )
176200 .set (tileGrid .getGridSize ().getX (), pixelZ , color , height , blockLight );
177201 }
178202
179203 if (pixelZ == 0 ) {
180- getTile (cellX , cellZ - 1 )
204+ accessTile (cellX , cellZ - 1 )
181205 .set (pixelX , tileGrid .getGridSize ().getY (), color , height , blockLight );
182206 }
183207
184208 if (pixelX == 0 && pixelZ == 0 ) {
185- getTile (cellX - 1 , cellZ - 1 )
209+ accessTile (cellX - 1 , cellZ - 1 )
186210 .set (tileGrid .getGridSize ().getX (), tileGrid .getGridSize ().getY (), color , height , blockLight );
187211 }
188212 }
0 commit comments