Очень медленная сборка пользовательского интерфейса React Native при добавлении / удалении текстовых элементов

Я создаю пользовательский компонент карты в React Native, который отображает карты процедурных миров из игры. Пока я могу панорамировать / масштабировать, переходить к положению, отображать маркеры и визуализировать наложение сетки. Однако у меня возникла проблема: на больших картах слишком много элементов, что замедляет рендеринг, и моя попытка отбраковки перегружает поток JS, потому что я добавляю / удаляю элементы, что по какой-то причине происходит очень медленно.

Мой подход к выбраковке - это просто запускать setState, когда нужно показать разные элементы. Это действительно работает, но поток JS падает почти до 0 кадров в секунду, а setStates выстраивается в очередь, поэтому очень неприятно наблюдать, как отбраковка делает свое дело долгое время после того, как вы прекращаете панорамирование.

Это мой код для части сетки (MapState управляет картой с помощью Reanimated):

import React, { useState, useEffect } from 'react';
import { View, Text } from 'react-native';
import Animated from 'react-native-reanimated';
import { cellSize, convertToMapPosition } from '../../game/Grid';
import { useMapState } from './MapState';

const { add, multiply } = Animated;

export function GridLayer() {
    const mapState = useMapState();
    const cellDims = cellSize * mapState.imageScale;

    const [state, setState] = useState(() => ({
        timer: startCulling(),
        showLabels: false,
        minX: 0,
        maxX: 0,
        minY: 0,
        maxY: 0,
    }));

    useEffect(() => {
        return () => clearInterval(state.timer);
    }, []);

    function startCulling() {
        let prevState = {} as typeof state;
        let inFlight =  false;

        function buildCullingState() {
            const scale = mapState.currentScale;

            const minXPos = -mapState.currentX / scale;
            const minYPos = -mapState.currentY / scale;
            const maxXPos = minXPos + (mapState.viewWidth / scale);
            const maxYPos = minYPos + (mapState.viewHeight / scale);

            const minX = Math.max(Math.floor(minXPos / cellDims) - 1, 0);
            const maxX = Math.max(Math.ceil(maxXPos / cellDims) + 2, 0);
            const minY = Math.max(Math.floor(minYPos / cellDims) - 1, 0);
            const maxY = Math.max(Math.ceil(maxYPos / cellDims) + 2, 0);

            const cellScreenSize = cellDims * scale;
            const showLabels = cellScreenSize >= 80;

            return { ...state, showLabels, minX, maxX, minY, maxY };
        }

        function doCulling() {
            if (inFlight) {
                return;
            }

            let newState = buildCullingState();
            if (newState.showLabels != prevState.showLabels ||
                newState.minX != prevState.minX ||
                newState.maxX != prevState.maxX ||
                newState.minY != prevState.minY ||
                newState.maxY != prevState.maxY
            ) {
                inFlight = true;
                setState(() => {
                    inFlight = false;
                    return buildCullingState();
                });
            }

            prevState = newState;
        }

        return setInterval(doCulling, 15);
    }

    const cellCount = Math.floor((mapState.imageWidth + cellDims - 1) / cellDims);
    const cellOffsets = [];
    for (let i = 0; i < cellCount; i++) {
        cellOffsets.push(i * cellDims);
    }
    const cellOffsetsW = cellOffsets.slice(state.minX, state.maxX);
    const cellOffsetsH = cellOffsets.slice(state.minY, state.maxY);

    const posWidth = 40;
    const posHeight = 25;
    const posAdjustX = multiply(posWidth, add(-1, mapState.scaleInv), 0.5);
    const posAdjustY = multiply(posHeight, add(-1, mapState.scaleInv), 0.5);

    return (
        <View style={{ position: 'absolute', width: 8192, height: 8192 }}>
            {cellOffsetsW.map(x =>
                <Animated.View
                    key={`X-${x}`}
                    style={{
                        position: 'absolute',
                        left: x,
                        top: 0,
                        width: mapState.scaleInv,
                        height: mapState.mapHeight,
                        backgroundColor: '#0007',
                    }} />
            )}

            {cellOffsetsH.map(y =>
                <Animated.View
                    key={`Y-${y}`}
                    style={{
                        position: 'absolute',
                        left: 0,
                        top: y,
                        width: mapState.mapWidth,
                        height: mapState.scaleInv,
                        backgroundColor: '#0007',
                    }} />
            )}

            {state.showLabels && cellOffsetsW.flatMap(x => cellOffsetsH.map(y =>
                <Animated.View
                    key={`L-${x}-${y}`}
                    style={{
                        position: 'absolute',
                        left: x,
                        top: y,
                        width: posWidth,
                        height: posHeight,
                        margin: 4,
                        transform: [
                            { translateX: posAdjustX },
                            { translateY: posAdjustY },
                            { scale: mapState.scaleInv },
                        ],
                    }}>

                    <Text style={{ color: '#000a' }}>
                        {convertToMapPosition(x / mapState.imageScale, y / mapState.imageScale)}
                    </Text>
                </Animated.View>
            ))}

        </View>
    );
}

Я пробовал несколько вещей, чтобы уменьшить количество вызовов setState, но не добился хороших результатов.

Что я могу сделать, чтобы это ускорить? Возможно, переработка старых элементов? Я мог бы использовать разные подходы? Я хочу остаться на Expo, но до сих пор я пробовал использовать React Native Maps (моя карта не в нужном формате плитки, понятия не имею о сетках) и сам рендеринг с помощью GLView+Pixijs (не удалось получить ввод рабочий).

0 ответов

Другие вопросы по тегам