Skip to content

Commit 05e12c5

Browse files
committed
Extract region/chunk-loading/caching from mcaworld into generic ChunkGrid class
1 parent ef4d8e7 commit 05e12c5

File tree

4 files changed

+183
-111
lines changed

4 files changed

+183
-111
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package de.bluecolored.bluemap.core.world.mca;
2+
3+
import com.flowpowered.math.vector.Vector2i;
4+
import com.github.benmanes.caffeine.cache.Caffeine;
5+
import com.github.benmanes.caffeine.cache.LoadingCache;
6+
import de.bluecolored.bluemap.core.BlueMap;
7+
import de.bluecolored.bluemap.core.logger.Logger;
8+
import de.bluecolored.bluemap.core.util.Vector2iCache;
9+
import de.bluecolored.bluemap.core.util.WatchService;
10+
import de.bluecolored.bluemap.core.world.ChunkConsumer;
11+
import de.bluecolored.bluemap.core.world.Region;
12+
import de.bluecolored.bluemap.core.world.mca.region.RegionType;
13+
import lombok.RequiredArgsConstructor;
14+
15+
import java.io.IOException;
16+
import java.nio.file.Files;
17+
import java.nio.file.Path;
18+
import java.util.Collection;
19+
import java.util.Collections;
20+
import java.util.List;
21+
import java.util.Objects;
22+
import java.util.concurrent.TimeUnit;
23+
import java.util.function.Predicate;
24+
import java.util.stream.Stream;
25+
26+
@RequiredArgsConstructor
27+
public class ChunkGrid<T> {
28+
private static final Vector2iCache VECTOR_2_I_CACHE = new Vector2iCache();
29+
30+
private final ChunkLoader<T> chunkLoader;
31+
private final Path regionFolder;
32+
33+
private final LoadingCache<Vector2i, Region<T>> regionCache = Caffeine.newBuilder()
34+
.executor(BlueMap.THREAD_POOL)
35+
.softValues()
36+
.maximumSize(32)
37+
.expireAfterWrite(10, TimeUnit.MINUTES)
38+
.expireAfterAccess(1, TimeUnit.MINUTES)
39+
.build(this::loadRegion);
40+
private final LoadingCache<Vector2i, T> chunkCache = Caffeine.newBuilder()
41+
.executor(BlueMap.THREAD_POOL)
42+
.softValues()
43+
.maximumSize(10240) // 10 regions worth of chunks
44+
.expireAfterWrite(10, TimeUnit.MINUTES)
45+
.expireAfterAccess(1, TimeUnit.MINUTES)
46+
.build(this::loadChunk);
47+
48+
public T getChunk(int x, int z) {
49+
return getChunk(VECTOR_2_I_CACHE.get(x, z));
50+
}
51+
52+
private T getChunk(Vector2i pos) {
53+
return chunkCache.get(pos);
54+
}
55+
56+
public Region<T> getRegion(int x, int z) {
57+
return getRegion(VECTOR_2_I_CACHE.get(x, z));
58+
}
59+
60+
private Region<T> getRegion(Vector2i pos) {
61+
return regionCache.get(pos);
62+
}
63+
64+
public void iterateChunks(int minX, int minZ, int maxX, int maxZ, ChunkConsumer<T> chunkConsumer) {
65+
66+
}
67+
68+
public void preloadRegionChunks(int x, int z, Predicate<Vector2i> chunkFilter) {
69+
try {
70+
getRegion(x, z).iterateAllChunks(new ChunkConsumer<>() {
71+
@Override
72+
public boolean filter(int chunkX, int chunkZ, int lastModified) {
73+
Vector2i chunkPos = VECTOR_2_I_CACHE.get(chunkX, chunkZ);
74+
return chunkFilter.test(chunkPos);
75+
}
76+
77+
@Override
78+
public void accept(int chunkX, int chunkZ, T chunk) {
79+
Vector2i chunkPos = VECTOR_2_I_CACHE.get(chunkX, chunkZ);
80+
chunkCache.put(chunkPos, chunk);
81+
}
82+
});
83+
} catch (IOException ex) {
84+
Logger.global.logDebug("Unexpected exception trying to load preload region (x:" + x + ", z:" + z + "): " + ex);
85+
}
86+
}
87+
88+
public Collection<Vector2i> listRegions() {
89+
if (!Files.exists(regionFolder)) return Collections.emptyList();
90+
try (Stream<Path> stream = Files.list(regionFolder)) {
91+
return stream
92+
.map(file -> {
93+
try {
94+
if (Files.size(file) <= 0) return null;
95+
return RegionType.regionForFileName(file.getFileName().toString());
96+
} catch (IOException ex) {
97+
Logger.global.logError("Failed to read region-file: " + file, ex);
98+
return null;
99+
}
100+
})
101+
.filter(Objects::nonNull)
102+
.toList();
103+
} catch (IOException ex) {
104+
Logger.global.logError("Failed to list regions for folder: '" + regionFolder + "'", ex);
105+
return List.of();
106+
}
107+
}
108+
109+
public WatchService<Vector2i> createRegionWatchService() throws IOException {
110+
return new MCAWorldRegionWatchService(this.regionFolder);
111+
}
112+
113+
public void invalidateChunkCache() {
114+
regionCache.invalidateAll();
115+
chunkCache.invalidateAll();
116+
}
117+
118+
public void invalidateChunkCache(int x, int z) {
119+
regionCache.invalidate(VECTOR_2_I_CACHE.get(x >> 5, z >> 5));
120+
chunkCache.invalidate(VECTOR_2_I_CACHE.get(x, z));
121+
}
122+
123+
private Region<T> loadRegion(Vector2i regionPos) {
124+
return loadRegion(regionPos.getX(), regionPos.getY());
125+
}
126+
127+
private Region<T> loadRegion(int x, int z) {
128+
return RegionType.loadRegion(chunkLoader, regionFolder, x, z);
129+
}
130+
131+
private T loadChunk(Vector2i chunkPos) {
132+
return loadChunk(chunkPos.getX(), chunkPos.getY());
133+
}
134+
135+
private T loadChunk(int x, int z) {
136+
final int tries = 3;
137+
final int tryInterval = 1000;
138+
139+
Exception loadException = null;
140+
for (int i = 0; i < tries; i++) {
141+
try {
142+
return getRegion(x >> 5, z >> 5)
143+
.loadChunk(x, z);
144+
} catch (IOException | RuntimeException e) {
145+
if (loadException != null && loadException != e)
146+
e.addSuppressed(loadException);
147+
148+
loadException = e;
149+
150+
if (i + 1 < tries) {
151+
try {
152+
Thread.sleep(tryInterval);
153+
} catch (InterruptedException ex) {
154+
Thread.currentThread().interrupt();
155+
break;
156+
}
157+
}
158+
}
159+
}
160+
161+
Logger.global.logDebug("Unexpected exception trying to load chunk (x:" + x + ", z:" + z + "): " + loadException);
162+
return chunkLoader.erroredChunk();
163+
}
164+
165+
}

core/src/main/java/de/bluecolored/bluemap/core/world/mca/ChunkLoader.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,6 @@ public interface ChunkLoader<T> {
3434

3535
T emptyChunk();
3636

37+
T erroredChunk();
38+
3739
}

core/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java

Lines changed: 11 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,6 @@ public class MCAWorld implements World {
6666
private static final Grid CHUNK_GRID = new Grid(16);
6767
private static final Grid REGION_GRID = new Grid(32).multiply(CHUNK_GRID);
6868

69-
private static final Vector2iCache VECTOR_2_I_CACHE = new Vector2iCache();
70-
7169
private final String id;
7270
private final Path worldFolder;
7371
private final Key dimension;
@@ -77,23 +75,8 @@ public class MCAWorld implements World {
7775
private final DimensionType dimensionType;
7876
private final Vector3i spawnPoint;
7977
private final Path dimensionFolder;
80-
private final Path regionFolder;
8178

82-
private final MCAChunkLoader chunkLoader = new MCAChunkLoader(this);
83-
private final LoadingCache<Vector2i, Region<Chunk>> regionCache = Caffeine.newBuilder()
84-
.executor(BlueMap.THREAD_POOL)
85-
.softValues()
86-
.maximumSize(32)
87-
.expireAfterWrite(10, TimeUnit.MINUTES)
88-
.expireAfterAccess(1, TimeUnit.MINUTES)
89-
.build(this::loadRegion);
90-
private final LoadingCache<Vector2i, Chunk> chunkCache = Caffeine.newBuilder()
91-
.executor(BlueMap.THREAD_POOL)
92-
.softValues()
93-
.maximumSize(10240) // 10 regions worth of chunks
94-
.expireAfterWrite(10, TimeUnit.MINUTES)
95-
.expireAfterAccess(1, TimeUnit.MINUTES)
96-
.build(this::loadChunk);
79+
private final ChunkGrid<Chunk> blockChunkGrid;
9780

9881
private MCAWorld(Path worldFolder, Key dimension, DataPack dataPack, LevelData levelData) {
9982
this.id = World.id(worldFolder, dimension);
@@ -121,7 +104,9 @@ private MCAWorld(Path worldFolder, Key dimension, DataPack dataPack, LevelData l
121104
levelData.getData().getSpawnZ()
122105
);
123106
this.dimensionFolder = resolveDimensionFolder(worldFolder, dimension);
124-
this.regionFolder = dimensionFolder.resolve("region");
107+
108+
this.blockChunkGrid = new ChunkGrid<>(new MCAChunkLoader(this), dimensionFolder.resolve("region"));
109+
125110
}
126111

127112
@Override
@@ -146,129 +131,44 @@ public Chunk getChunkAtBlock(int x, int z) {
146131

147132
@Override
148133
public Chunk getChunk(int x, int z) {
149-
return getChunk(VECTOR_2_I_CACHE.get(x, z));
150-
}
151-
152-
private Chunk getChunk(Vector2i pos) {
153-
return chunkCache.get(pos);
134+
return blockChunkGrid.getChunk(x, z);
154135
}
155136

156137
@Override
157138
public Region<Chunk> getRegion(int x, int z) {
158-
return getRegion(VECTOR_2_I_CACHE.get(x, z));
159-
}
160-
161-
private Region<Chunk> getRegion(Vector2i pos) {
162-
return regionCache.get(pos);
139+
return blockChunkGrid.getRegion(x, z);
163140
}
164141

165142
@Override
166143
public Collection<Vector2i> listRegions() {
167-
if (!Files.exists(regionFolder)) return Collections.emptyList();
168-
try (Stream<Path> stream = Files.list(regionFolder)) {
169-
return stream
170-
.map(file -> {
171-
try {
172-
if (Files.size(file) <= 0) return null;
173-
return RegionType.regionForFileName(file.getFileName().toString());
174-
} catch (IOException ex) {
175-
Logger.global.logError("Failed to read region-file: " + file, ex);
176-
return null;
177-
}
178-
})
179-
.filter(Objects::nonNull)
180-
.toList();
181-
} catch (IOException ex) {
182-
Logger.global.logError("Failed to list regions for world: '" + getId() + "'", ex);
183-
return List.of();
184-
}
144+
return blockChunkGrid.listRegions();
185145
}
186146

187147
@Override
188148
public WatchService<Vector2i> createRegionWatchService() throws IOException {
189-
return new MCAWorldRegionWatchService(this.regionFolder);
149+
return blockChunkGrid.createRegionWatchService();
190150
}
191151

192152
@Override
193153
public void preloadRegionChunks(int x, int z, Predicate<Vector2i> chunkFilter) {
194-
try {
195-
getRegion(x, z).iterateAllChunks(new ChunkConsumer<>() {
196-
@Override
197-
public boolean filter(int chunkX, int chunkZ, int lastModified) {
198-
Vector2i chunkPos = VECTOR_2_I_CACHE.get(chunkX, chunkZ);
199-
return chunkFilter.test(chunkPos);
200-
}
201-
202-
@Override
203-
public void accept(int chunkX, int chunkZ, Chunk chunk) {
204-
Vector2i chunkPos = VECTOR_2_I_CACHE.get(chunkX, chunkZ);
205-
chunkCache.put(chunkPos, chunk);
206-
}
207-
});
208-
} catch (IOException ex) {
209-
Logger.global.logDebug("Unexpected exception trying to load preload region (x:" + x + ", z:" + z + "): " + ex);
210-
}
154+
blockChunkGrid.preloadRegionChunks(x, z, chunkFilter);
211155
}
212156

213157
@Override
214158
public void invalidateChunkCache() {
215-
regionCache.invalidateAll();
216-
chunkCache.invalidateAll();
159+
blockChunkGrid.invalidateChunkCache();
217160
}
218161

219162
@Override
220163
public void invalidateChunkCache(int x, int z) {
221-
regionCache.invalidate(VECTOR_2_I_CACHE.get(x >> 5, z >> 5));
222-
chunkCache.invalidate(VECTOR_2_I_CACHE.get(x, z));
164+
blockChunkGrid.invalidateChunkCache(x, z);
223165
}
224166

225167
@Override
226168
public void iterateEntities(int minX, int minZ, int maxX, int maxZ, Consumer<Entity> entityConsumer) {
227169
//TODO
228170
}
229171

230-
private Region<Chunk> loadRegion(Vector2i regionPos) {
231-
return loadRegion(regionPos.getX(), regionPos.getY());
232-
}
233-
234-
private Region<Chunk> loadRegion(int x, int z) {
235-
return RegionType.loadRegion(chunkLoader, getRegionFolder(), x, z);
236-
}
237-
238-
private Chunk loadChunk(Vector2i chunkPos) {
239-
return loadChunk(chunkPos.getX(), chunkPos.getY());
240-
}
241-
242-
private Chunk loadChunk(int x, int z) {
243-
final int tries = 3;
244-
final int tryInterval = 1000;
245-
246-
Exception loadException = null;
247-
for (int i = 0; i < tries; i++) {
248-
try {
249-
return getRegion(x >> 5, z >> 5)
250-
.loadChunk(x, z);
251-
} catch (IOException | RuntimeException e) {
252-
if (loadException != null && loadException != e)
253-
e.addSuppressed(loadException);
254-
255-
loadException = e;
256-
257-
if (i + 1 < tries) {
258-
try {
259-
Thread.sleep(tryInterval);
260-
} catch (InterruptedException ex) {
261-
Thread.currentThread().interrupt();
262-
break;
263-
}
264-
}
265-
}
266-
}
267-
268-
Logger.global.logDebug("Unexpected exception trying to load chunk (x:" + x + ", z:" + z + "): " + loadException);
269-
return Chunk.ERRORED_CHUNK;
270-
}
271-
272172
public static MCAWorld load(Path worldFolder, Key dimension, DataPack dataPack) throws IOException, InterruptedException {
273173

274174
// load level.dat

core/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/MCAChunkLoader.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ public Chunk emptyChunk() {
8686
return Chunk.EMPTY_CHUNK;
8787
}
8888

89+
@Override
90+
public Chunk erroredChunk() {
91+
return Chunk.ERRORED_CHUNK;
92+
}
93+
8994
private @Nullable ChunkVersionLoader<?> findBestLoaderForVersion(int version) {
9095
for (ChunkVersionLoader<?> loader : CHUNK_VERSION_LOADERS) {
9196
if (loader.mightSupport(version)) return loader;

0 commit comments

Comments
 (0)