std::map<UnwrappedTileID, Renderable> updateRenderables(const DataTiles& dataTiles,
                                                        const IdealTileIDs& idealTileIDs,
                                                        const SourceInfo& info,
                                                        const uint8_t z) {
    std::map<UnwrappedTileID, Renderable> renderables;

    // for (all in the set of ideal tiles of the source) {
    for (const auto& renderTileID : idealTileIDs) {
        assert(renderTileID.canonical.z >= info.minZoom);
        assert(renderTileID.canonical.z <= info.maxZoom);
        assert(z >= renderTileID.canonical.z);
        const auto wrap = renderTileID.wrap;
        const OverscaledTileID dataTileID(z, renderTileID.canonical);

        // if (source has the tile and bucket is loaded) {
        if (!tryTile(renderTileID, dataTileID, dataTiles, renderables)) {
            // The source doesn't have the tile, or the bucket isn't loaded.
            bool covered = true;
            int32_t overscaledZ = z + 1;
            if (overscaledZ > info.maxZoom) {
                // We're looking for an overzoomed child tile.
                const auto childDataTileID = dataTileID.scaledTo(overscaledZ);
                if (!tryTile(renderTileID, childDataTileID, dataTiles, renderables)) {
                    covered = false;
                }
            } else {
                // Check all four actual child tiles.
                for (const auto& childTileID : dataTileID.canonical.children()) {
                    const OverscaledTileID childDataTileID(overscaledZ, childTileID);
                    const UnwrappedTileID childRenderTileID(wrap, childTileID);
                    if (!tryTile(childRenderTileID, childDataTileID, dataTiles, renderables)) {
                        // At least one child tile doesn't exist, so we are going to look for
                        // parents as well.
                        covered = false;
                    }
                }
            }

            if (!covered) {
                // We couldn't find child tiles that entirely cover the ideal tile.
                for (overscaledZ = z - 1; overscaledZ >= info.minZoom; --overscaledZ) {
                    const auto parentDataTileID = dataTileID.scaledTo(overscaledZ);
                    const auto parentRenderTileID = parentDataTileID.unwrapTo(renderTileID.wrap);
                    if (tryTile(parentRenderTileID, parentDataTileID, dataTiles, renderables)) {
                        // Break parent tile ascent, since we found one.
                        break;
                    }
                }
            }
        }
    }

    return renderables;
}
void updateRenderables(GetTileFn getTile,
                       CreateTileFn createTile,
                       RetainTileFn retainTile,
                       RenderTileFn renderTile,
                       const IdealTileIDs& idealTileIDs,
                       const Range<uint8_t>& zoomRange,
                       const uint8_t dataTileZoom) {
    std::unordered_set<OverscaledTileID> checked;
    bool covered;
    int32_t overscaledZ;

    // for (all in the set of ideal tiles of the source) {
    for (const auto& idealRenderTileID : idealTileIDs) {
        assert(idealRenderTileID.canonical.z >= zoomRange.min);
        assert(idealRenderTileID.canonical.z <= zoomRange.max);
        assert(dataTileZoom >= idealRenderTileID.canonical.z);

        const OverscaledTileID idealDataTileID(dataTileZoom, idealRenderTileID.wrap, idealRenderTileID.canonical);
        auto tile = getTile(idealDataTileID);
        if (!tile) {
            tile = createTile(idealDataTileID);
            // For source types where TileJSON.bounds is set, tiles outside the
            // bounds are not created
            if(tile == nullptr) {
                continue;
            }
        }

        // if (source has the tile and bucket is loaded) {
        if (tile->isRenderable()) {
            retainTile(*tile, TileNecessity::Required);
            renderTile(idealRenderTileID, *tile);
        } else {
            // We are now attempting to load child and parent tiles.
            bool parentHasTriedOptional = tile->hasTriedCache();
            bool parentIsLoaded = tile->isLoaded();

            // The tile isn't loaded yet, but retain it anyway because it's an ideal tile.
            retainTile(*tile, TileNecessity::Required);
            covered = true;
            overscaledZ = dataTileZoom + 1;
            if (overscaledZ > zoomRange.max) {
                // We're looking for an overzoomed child tile.
                const auto childDataTileID = idealDataTileID.scaledTo(overscaledZ);
                tile = getTile(childDataTileID);
                if (tile && tile->isRenderable()) {
                    retainTile(*tile, TileNecessity::Optional);
                    renderTile(idealRenderTileID, *tile);
                } else {
                    covered = false;
                }
            } else {
                // Check all four actual child tiles.
                for (const auto& childTileID : idealDataTileID.canonical.children()) {
                    const OverscaledTileID childDataTileID(overscaledZ, idealRenderTileID.wrap, childTileID);
                    tile = getTile(childDataTileID);
                    if (tile && tile->isRenderable()) {
                        retainTile(*tile, TileNecessity::Optional);
                        renderTile(childDataTileID.toUnwrapped(), *tile);
                    } else {
                        // At least one child tile doesn't exist, so we are going to look for
                        // parents as well.
                        covered = false;
                    }
                }
            }

            if (!covered) {
                // We couldn't find child tiles that entirely cover the ideal tile.
                for (overscaledZ = dataTileZoom - 1; overscaledZ >= zoomRange.min; --overscaledZ) {
                    const auto parentDataTileID = idealDataTileID.scaledTo(overscaledZ);

                    if (checked.find(parentDataTileID) != checked.end()) {
                        // Break parent tile ascent, this route has been checked by another child
                        // tile before.
                        break;
                    } else {
                        checked.emplace(parentDataTileID);
                    }

                    tile = getTile(parentDataTileID);
                    if (!tile && (parentHasTriedOptional || parentIsLoaded)) {
                        tile = createTile(parentDataTileID);
                    }

                    if (tile) {
                        if (!parentIsLoaded) {
                            // We haven't completed loading the child, so we only do an optional
                            // (cache) request in an attempt to quickly load data that we can show.
                            retainTile(*tile, TileNecessity::Optional);
                        } else {
                            // Now that we've checked the child and know for sure that we can't load
                            // it, we attempt to load the parent from the network.
                            retainTile(*tile, TileNecessity::Required);
                        }

                        // Save the current values, since they're the parent of the next iteration
                        // of the parent tile ascent loop.
                        parentHasTriedOptional = tile->hasTriedCache();
                        parentIsLoaded = tile->isLoaded();

                        if (tile->isRenderable()) {
                            renderTile(parentDataTileID.toUnwrapped(), *tile);
                            // Break parent tile ascent, since we found one.
                            break;
                        }
                    }
                }
            }
        }
    }
}