Реализация поточно-ориентированных HashMaps очень большого размера - PullRequest
0 голосов
/ 03 марта 2019

В настоящее время я пишу воксельную игру и натолкнулся на следующую проблему: при чтении четырехугольников с большого (а также изменчивого!) 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 && 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().Это гарантирует, что для записи на карту потребуется как можно меньше времени.

Я хочу пояснить, что синхронизация чтения - это опция НЕ , так как это замедлит мой рендеринг.,Я предпочел бы иметь время вычислений, необходимое для перехода в поток генерации чанков, чем поток рендеринга (в данном случае «основной» поток).

Любая помощь очень ценится, спасибо!

РЕДАКТИРОВАТЬ: мой рендерер работает стабильно на скорости 60 кадров в секунду, но, кажется, случайным образом просто зависает и время от времени перемещается до 1 кадра в секунду, я думаю, что эти проблемы связаны, и любой вклад относительно этого также велик.Я только что понял, что renderables эффективно неизменный.Я очищаю его и помещаю в него все содержимое quads.

РЕШЕНО: Я создал резервную копию карты, которую я очищаю после обновления renderables, и добавляю все текущие четырехугольники.Затем я обернул это в synchronized блок и сделал то же самое для геттера.Мерцание исчезло, как и странное исключение нулевого указателя.Оставьте открытыми вопросы для ответов на EDIT # 1.

...