#java #multithreading #opengl #concurrenthashmap
#java #многопоточность #opengl #concurrenthashmap
Вопрос:
В настоящее время я кодирую воксельную игру и наткнулся на следующую проблему: При чтении моих квадратов из большого (и также изменчивого!) ConcurrentHashMap
Я получаю эффект мерцания на своем экране, и в редких случаях моя функция получения просто возвращает null
. Здесь HashMap
используются целые числа (представленные классом GLTexture
) для хранения идентификаторов текстур в качестве ключей и есть ArrayList
объекты, содержащие Quad
объекты в качестве значений. Эти списки могут достигать общей емкости до 40000. Графический процессор прекрасно справляется с этим, поскольку я использую очень простой (но очень эффективный!) рендеринг с экземплярами, но при генерации фрагмента (который выполняется в отдельном потоке, отсюда и причина этого поста), похоже, возникают проблемы с записью на эту карту, пока программа визуализации пытается прочитать из нее.
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 amp;amp; neighbor.getAbsoluteZ() == chunk.getAbsoluteZ()) {
x0 = neighbor;
} else if (neighbor.getAbsoluteX() == chunk.getAbsoluteX() 16 amp;amp; neighbor.getAbsoluteZ() == chunk.getAbsoluteZ()) {
x1 = neighbor;
} else if (neighbor.getAbsoluteX() == chunk.getAbsoluteX() amp;amp; neighbor.getAbsoluteZ() == chunk.getAbsoluteZ()-16) {
z0 = neighbor;
} else if (neighbor.getAbsoluteX() == chunk.getAbsoluteX() amp;amp; 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 amp;amp; 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 amp;amp; 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 amp;amp; 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 amp;amp; 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 amp;amp; 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 amp;amp;
x0.getCube(Chunk.CHUNK_SIZE-1, y, z).getType() != BlockType.AIR amp;amp;
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 amp;amp;
x1.getCube(0, y, z).getType() != BlockType.AIR amp;amp;
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 amp;amp;
z0.getCube(x, y, Chunk.CHUNK_SIZE-1).getType() != BlockType.AIR amp;amp;
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 amp;amp;
z1.getCube(x, y, 0).getType() != BlockType.AIR amp;amp;
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()
. Это гарантирует, что запись на карту займет как можно меньше времени.
Я хочу предельно ясно дать понять, что синхронизация чтения НЕ является вариантом, поскольку это замедлило бы мой рендеринг. Я бы предпочел, чтобы время вычислений, необходимое для перехода в поток генерации фрагмента, превышало поток рендеринга (в данном случае «основной» поток).
Любая помощь приветствуется, спасибо!
РЕДАКТИРОВАТЬ: Мой рендерер стабильно работает со скоростью 60 кадров в секунду, но, похоже, время от времени случайным образом зависает и снижается до 1 кадра в секунду, я думаю, что эти проблемы связаны, и любой вклад в это также хорош.
РЕДАКТИРОВАТЬ: я только что понял, что renderables
это фактически неизменяемый. Я очищаю его и помещаю в него все содержимое quads
.
РЕШАЕМАЯ: Я реализовал резервную копию, которую очищаю после обновления renderables
и добавляю все текущие квадраты. Затем я заключил это в synchronized
блок и сделал то же самое для средства получения. Исчезло мерцание, а также странное исключение нулевого указателя. Оставляем вопросы открытыми для получения ответов на РЕДАКТИРОВАНИЕ # 1.
Комментарии:
1. Интересно, не могли бы вы подробнее остановиться на этом? Вы имеете в виду
genRenderables()
метод?2. Возможно, он имеет в виду updateNeighbors и genChunk. Уровни вложенности тоже не помогают. Но я понимаю, что пространственная логика игр может стать громоздкой.
3. Однако я не вижу лучшего способа проверить окружающие грани блоков. Я все еще собираюсь убрать проверку «многослойных» сплошных слоев, которые невидимы, чтобы сократить время.
4. Я только что отредактировал
genRenderables()
метод. Это то, о чем вы думали? @GhostCat5. Я говорю о genChunks(). Но, вероятно, мой совет был неправильным, поскольку я заблудился в слишком сложном источнике этого метода 🙂