我目前正在编码一个体素游戏,但偶然发现以下问题:从大的(而且也是易失的!)读取四边形时ConcurrentHashMap
,我的屏幕上出现闪烁效果,在极少数情况下,我的消气剂函数仅返回null
。该HashMap
使用整数(由类GLTexture
表示)来存储纹理ID作为键,并具有ArrayList
个对象,其中包含Quad
个对象作为值。这些列表的总容量可以达到40000。GPU可以很好地处理此问题,因为我使用的是非常基本(但非常有效!)的实例渲染,但是块生成(在单独的线程上运行,因此是本文的原因)渲染器尝试从中读取地图时,似乎无法写入该地图。
public class ChunkMeshGenerator {
private static volatile Map<Chunk, Map<GLTexture, List<Quad>>> quads;
private static volatile Map<GLTexture, List<Quad>> renderables;
static {
quads = new ConcurrentHashMap<Chunk, Map<GLTexture, List<Quad>>>();
renderables = new ConcurrentHashMap<GLTexture, List<Quad>>();
}
public static void genChunk (Chunk chunk) {
List<Quad> temp = new ArrayList<Quad>();
Chunk x0 = null;
Chunk x1 = null;
Chunk z0 = null;
Chunk z1 = null;
synchronized (quads) {
for (Chunk neighbor : quads.keySet()) {
if (neighbor.getAbsoluteX() == chunk.getAbsoluteX()-16 && neighbor.getAbsoluteZ() == chunk.getAbsoluteZ()) {
x0 = neighbor;
} else if (neighbor.getAbsoluteX() == chunk.getAbsoluteX()+16 && neighbor.getAbsoluteZ() == chunk.getAbsoluteZ()) {
x1 = neighbor;
} else if (neighbor.getAbsoluteX() == chunk.getAbsoluteX() && neighbor.getAbsoluteZ() == chunk.getAbsoluteZ()-16) {
z0 = neighbor;
} else if (neighbor.getAbsoluteX() == chunk.getAbsoluteX() && neighbor.getAbsoluteZ() == chunk.getAbsoluteZ()+16) {
z1 = neighbor;
}
}
}
for (int x = 0; x < Chunk.CHUNK_SIZE; x++) {
for (int y = 0; y < Chunk.CHUNK_HEIGHT; y++) {
for (int z = 0; z < Chunk.CHUNK_SIZE; z++) {
if (chunk.getCube(x, y, z).getType() == BlockType.AIR) continue;
if (x == Chunk.CHUNK_SIZE-1) {
if (x1 != null && x1.getCube(0, y, z).getType() == BlockType.AIR) {
temp.add(chunk.getCube(x, y, z).getFace(Cube.RIGHT));
}
} else if (chunk.getCube(x+1, y, z).getType() == BlockType.AIR) {
temp.add(chunk.getCube(x, y, z).getFace(Cube.RIGHT));
}
if (x == 0) {
if (x0 != null && x0.getCube(Chunk.CHUNK_SIZE-1, y, z).getType() == BlockType.AIR) {
temp.add(chunk.getCube(x, y, z).getFace(Cube.LEFT));
}
} else if (chunk.getCube(x-1, y, z).getType() == BlockType.AIR) {
temp.add(chunk.getCube(x, y, z).getFace(Cube.LEFT));
}
if (y == Chunk.CHUNK_HEIGHT-1) {
temp.add(chunk.getCube(x, y, z).getFace(Cube.TOP));
} else if (chunk.getCube(x, y+1, z).getType() == BlockType.AIR) {
temp.add(chunk.getCube(x, y, z).getFace(Cube.TOP));
}
if (y != 0 && chunk.getCube(x, y-1, z).getType() == BlockType.AIR) {
temp.add(chunk.getCube(x, y, z).getFace(Cube.BOTTOM));
}
if (z == Chunk.CHUNK_SIZE-1) {
if (z1 != null && z1.getCube(x, y, 0).getType() == BlockType.AIR) {
temp.add(chunk.getCube(x, y, z).getFace(Cube.BACK));
}
} else if (chunk.getCube(x, y, z+1).getType() == BlockType.AIR) {
temp.add(chunk.getCube(x, y, z).getFace(Cube.BACK));
}
if (z == 0) {
if (z0 != null && z0.getCube(x, y, Chunk.CHUNK_SIZE-1).getType() == BlockType.AIR) {
temp.add(chunk.getCube(x, y, z).getFace(Cube.FRONT));
}
} else if (chunk.getCube(x, y, z-1).getType() == BlockType.AIR) {
temp.add(chunk.getCube(x, y, z).getFace(Cube.FRONT));
}
}
}
}
List<Chunk> neighbors = new ArrayList<Chunk>();
neighbors.add(x0);
neighbors.add(x1);
neighbors.add(z0);
neighbors.add(z1);
updateNeighbors(chunk, neighbors);
Map<GLTexture, List<Quad>> map = quads.get(chunk);
if (map == null) {
map = new ConcurrentHashMap<GLTexture, List<Quad>>();
quads.put(chunk, map);
}
for (Quad quad : temp) {
List<Quad> batch = map.get(quad.getTexture());
if (batch == null) {
batch = new ArrayList<Quad>();
map.put(quad.getTexture(), batch);
}
batch.add(quad);
}
genRenderables();
}
private static void updateNeighbors (Chunk chunk, List<Chunk> neighbors) {
Chunk x0 = neighbors.get(0);
Chunk x1 = neighbors.get(1);
Chunk z0 = neighbors.get(2);
Chunk z1 = neighbors.get(3);
for (int x = 0; x < Chunk.CHUNK_SIZE; x++) {
for (int z = 0; z < Chunk.CHUNK_SIZE; z++) {
for (int y = 0; y < Chunk.CHUNK_HEIGHT; y++) {
if (x0 != null &&
x0.getCube(Chunk.CHUNK_SIZE-1, y, z).getType() != BlockType.AIR &&
chunk.getCube(0, y, z).getType() == BlockType.AIR) {
Map<GLTexture, List<Quad>> chunkQuads = quads.get(x0);
if (chunkQuads == null) {
chunkQuads = new ConcurrentHashMap<GLTexture, List<Quad>>();
quads.put(x0, chunkQuads);
}
Quad face = x0.getCube(Chunk.CHUNK_SIZE-1, y, z).getFace(Cube.RIGHT);
List<Quad> batch = chunkQuads.get(face.getTexture());
if (batch == null) {
batch = new SyncList<Quad>();
chunkQuads.put(face.getTexture(), batch);
}
batch.add(face);
}
if (x1 != null &&
x1.getCube(0, y, z).getType() != BlockType.AIR &&
chunk.getCube(Chunk.CHUNK_SIZE-1, y, z).getType() == BlockType.AIR) {
Map<GLTexture, List<Quad>> chunkQuads = quads.get(x1);
if (chunkQuads == null) {
chunkQuads = new ConcurrentHashMap<GLTexture, List<Quad>>();
quads.put(x1, chunkQuads);
}
Quad face = x1.getCube(0, y, z).getFace(Cube.LEFT);
List<Quad> batch = chunkQuads.get(face.getTexture());
if (batch == null) {
batch = new SyncList<Quad>();
chunkQuads.put(face.getTexture(), batch);
}
batch.add(face);
}
if (z0 != null &&
z0.getCube(x, y, Chunk.CHUNK_SIZE-1).getType() != BlockType.AIR &&
chunk.getCube(x, y, 0).getType() == BlockType.AIR) {
Map<GLTexture, List<Quad>> chunkQuads = quads.get(z0);
if (chunkQuads == null) {
chunkQuads = new ConcurrentHashMap<GLTexture, List<Quad>>();
quads.put(z0, chunkQuads);
}
Quad face = z0.getCube(x, y, Chunk.CHUNK_SIZE-1).getFace(Cube.BACK);
List<Quad> batch = chunkQuads.get(face.getTexture());
if (batch == null) {
batch = new SyncList<Quad>();
chunkQuads.put(face.getTexture(), batch);
}
batch.add(face);
}
if (z1 != null &&
z1.getCube(x, y, 0).getType() != BlockType.AIR &&
chunk.getCube(x, y, Chunk.CHUNK_SIZE-1).getType() == BlockType.AIR) {
Map<GLTexture, List<Quad>> chunkQuads = quads.get(z1);
if (chunkQuads == null) {
chunkQuads = new ConcurrentHashMap<GLTexture, List<Quad>>();
quads.put(z1, chunkQuads);
}
Quad face = z1.getCube(x, y, 0).getFace(Cube.FRONT);
List<Quad> batch = chunkQuads.get(face.getTexture());
if (batch == null) {
batch = new SyncList<Quad>();
chunkQuads.put(face.getTexture(), batch);
}
batch.add(face);
}
}
}
}
}
public static void removeChunk (Chunk chunk) {
quads.remove(chunk);
genRenderables();
}
public static Map<GLTexture, List<Quad>> getMesh () {
return renderables;
}
private static void genRenderables () {
renderables.clear();
for (Chunk chunk : quads.keySet()) {
for (GLTexture texture : quads.get(chunk).keySet()) {
renderables.putIfAbsent(texture, new ArrayList<Quad>());
renderables.get(texture).addAll(quads.get(chunk).get(texture));
}
}
}
}
这里的重点不是这些方法的功能,而是我实际修改quads
和renderables
映射的部分。
如您所见,我将生成的所有Quad
对象都写到quads
映射中。
修改功能始终以对genRenderables()
的调用结束。这样可以确保花费最少的时间写入地图。
我想非常清楚地表明,同步读取不是 NOT 选项,因为这会减慢我的渲染速度。我宁愿拥有进入块生成线程而不是呈现线程(在本例中为“主”线程)所需的计算时间。
非常感谢您的帮助!
编辑: 我的渲染器稳定地以60 fps的速度运行,但似乎只是不时地冻结并不时降低到1 fps,我认为这些问题是相关的,与此相关的任何输入也很棒。
编辑:
我刚刚意识到renderables
实际上是不可变的。我将其清除,并将quads
的所有内容放入其中。
已解决:
我实现了一个backupMap,它在更新renderables
并添加所有当前象限后清除。然后,我将其包装在synchronized
块中,并对吸气剂进行了同样的处理。闪烁以及奇怪的空指针异常都消失了。悬而未决的问题,以寻求答案1。