OSMAND_CORE_API void OSMAND_CORE_CALL OsmAnd::ReleaseCore()
{
    if (_qCoreApplicationThread)
    {
        QCoreApplication::exit();
        REPEAT_UNTIL(_qCoreApplicationThread->wait());
        _qCoreApplicationThread.reset();
    }
    else
    {
        releaseInAppThread();
    }

    // MapSymbol intersection classes registry
    MapSymbolIntersectionClassesRegistry_releaseGlobalInstance();

    // Text rasterizer
    TextRasterizer_releaseGlobalInstance();

    // ICU
    ICU::release();

    // SKIA
    SKIA::release();

    Logger::get()->flush();
    Logger::get()->removeAllLogSinks();
}
OSMAND_CORE_API bool OSMAND_CORE_CALL OsmAnd::InitializeCore(const std::shared_ptr<const ICoreResourcesProvider>& coreResourcesProvider)
{
    if (!coreResourcesProvider)
    {
        std::cerr << "OsmAnd core requires non-null core resources provider!" << std::endl;
        return false;
    }
    
    gCoreResourcesProvider = coreResourcesProvider;

    Logger::get()->addLogSink(std::shared_ptr<ILogSink>(new DefaultLogSink()));

    InflateExplicitReferences();

    if (!QCoreApplication::instance())
    {
        LogPrintf(LogSeverityLevel::Info,
            "OsmAnd Core is initialized standalone, so going to create 'application' thread");
        _qCoreApplicationThread.reset(new QCoreApplicationThread());
        gMainThread = _qCoreApplicationThread.get();
        _qCoreApplicationThread->start();

        // Wait until global initialization will pass in that thread
        {
            QMutexLocker scopeLock(&_qCoreApplicationThreadMutex);
            while (!_qCoreApplicationThread->wasInitialized)
                REPEAT_UNTIL(_qCoreApplicationThreadWaitCondition.wait(&_qCoreApplicationThreadMutex));
        }
    }
    else
    {
        LogPrintf(LogSeverityLevel::Info,
            "OsmAnd Core is initialized inside a Qt application, so assuming that got called from application thread");
        gMainThread = QThread::currentThread();
        initializeInAppThread();
    }

    // GDAL
    GDALAllRegister();

    // SKIA
    if (!SKIA::initialize())
        return false;

    // ICU
    if (!ICU::initialize())
        return false;

    // Qt
    (void)QLocale::system(); // This will initialize system locale, since it fails to initialize concurrently

    // Text rasterizer
    TextRasterizer_initializeGlobalInstance();

    // MapSymbol intersection classes registry
    MapSymbolIntersectionClassesRegistry_initializeGlobalInstance();

    return true;
}
Example #3
0
void OsmAnd::Concurrent::Dispatcher::shutdown()
{
    QMutexLocker scopedLocker(&_performedShutdownConditionMutex);

    shutdownAsync();

    REPEAT_UNTIL(_performedShutdown.wait(&_performedShutdownConditionMutex));
}
Example #4
0
void OsmAnd::ObfFile_P::lockForWriting() const
{
    QMutexLocker scopedLocker(&_lockCounterMutex);

    while(_lockCounter > 0)
        REPEAT_UNTIL(_lockCounterWaitCondition.wait(&_lockCounterMutex));
    _lockCounter--;
    _lockCounterWaitCondition.wakeAll();
}
Example #5
0
void OsmAnd::Concurrent::TaskHost::onOwnerIsBeingDestructed()
{
    // Mark that owner is being destructed
    _ownerIsBeingDestructed = true;

    // Ask all tasks to cancel
    {
        QReadLocker scopedLock(&_hostedTasksLock);

        for(const auto& task : constOf(_hostedTasks))
            task->requestCancellation();
    }

    // Hold until all tasks are released
    {
        QReadLocker scopedLocker(&_hostedTasksLock);
        while(_hostedTasks.size() != 0)
            REPEAT_UNTIL(_unlockedCondition.wait(&_hostedTasksLock));
    }
}
Example #6
0
void OsmAnd::Concurrent::Dispatcher::invoke(const Delegate method)
{
    assert(method != nullptr);

    QMutex waitMutex;
    QWaitCondition waitCondition;
    invokeAsync([&waitCondition, &waitMutex, method]
    {
        method();

        {
            QMutexLocker scopedLocker(&waitMutex);
            waitCondition.wakeAll();
        }
    });

    {
        QMutexLocker scopedLocker(&waitMutex);
        REPEAT_UNTIL(waitCondition.wait(&waitMutex));
    }
}
bool OsmAnd::MapPrimitivesProvider_P::obtainData(
    const TileId tileId,
    const ZoomLevel zoom,
    std::shared_ptr<MapPrimitivesProvider::Data>& outTiledData,
    MapPrimitivesProvider_Metrics::Metric_obtainData* const metric_,
    const IQueryController* const queryController)
{
#if OSMAND_PERFORMANCE_METRICS
    MapPrimitivesProvider_Metrics::Metric_obtainData localMetric;
    const auto metric = metric_ ? metric_ : &localMetric;
#else
    const auto metric = metric_;
#endif

    const Stopwatch totalStopwatch(metric != nullptr);

    std::shared_ptr<TileEntry> tileEntry;

    for (;;)
    {
        // Try to obtain previous instance of tile
        _tileReferences.obtainOrAllocateEntry(tileEntry, tileId, zoom,
            []
            (const TiledEntriesCollection<TileEntry>& collection, const TileId tileId, const ZoomLevel zoom) -> TileEntry*
            {
                return new TileEntry(collection, tileId, zoom);
            });

        // If state is "Undefined", change it to "Loading" and proceed with loading
        if (tileEntry->setStateIf(TileState::Undefined, TileState::Loading))
            break;

        // In case tile entry is being loaded, wait until it will finish loading
        if (tileEntry->getState() == TileState::Loading)
        {
            QReadLocker scopedLcoker(&tileEntry->loadedConditionLock);

            // If tile is in 'Loading' state, wait until it will become 'Loaded'
            while (tileEntry->getState() != TileState::Loaded)
                REPEAT_UNTIL(tileEntry->loadedCondition.wait(&tileEntry->loadedConditionLock));
        }

        if (!tileEntry->dataIsPresent)
        {
            // If there was no data, return same
            outTiledData.reset();
            return true;
        }
        else
        {
            // Otherwise, try to lock tile reference
            outTiledData = tileEntry->dataWeakRef.lock();

            // If successfully locked, just return it
            if (outTiledData)
                return true;

            // Otherwise consider this tile entry as expired, remove it from collection (it's safe to do that right now)
            // This will enable creation of new entry on next loop cycle
            _tileReferences.removeEntry(tileId, zoom);
            tileEntry.reset();
        }
    }

    const Stopwatch totalTimeStopwatch(
#if OSMAND_PERFORMANCE_METRICS
        true
#else
        metric != nullptr
#endif // OSMAND_PERFORMANCE_METRICS
        );

    // Obtain map objects data tile
    std::shared_ptr<IMapObjectsProvider::Data> dataTile;
    std::shared_ptr<Metric> submetric;
    owner->mapObjectsProvider->obtainData(
        tileId,
        zoom,
        dataTile,
        metric ? &submetric : nullptr,
        nullptr);
    if (metric && submetric)
        metric->addOrReplaceSubmetric(submetric);
    if (!dataTile)
    {
        // Store flag that there was no data and mark tile entry as 'Loaded'
        tileEntry->dataIsPresent = false;
        tileEntry->setState(TileState::Loaded);

        // Notify that tile has been loaded
        {
            QWriteLocker scopedLcoker(&tileEntry->loadedConditionLock);
            tileEntry->loadedCondition.wakeAll();
        }

        outTiledData.reset();
        return true;
    }

    // Get primitivised objects
    std::shared_ptr<MapPrimitiviser::PrimitivisedObjects> primitivisedObjects;
    if (owner->mode == MapPrimitivesProvider::Mode::AllObjectsWithoutPolygonFiltering)
    {
        primitivisedObjects = owner->primitiviser->primitiviseAllMapObjects(
            zoom,
            dataTile->mapObjects,
            //NOTE: So far it's safe to turn off this cache. But it has to be rewritten. Since lock/unlock occurs too often, this kills entire performance
            //NOTE: Maybe a QuadTree-based cache with leaf-only locking will save up much. Or use supernodes, like DataBlock
            nullptr, //_primitiviserCache,
            nullptr,
            metric ? metric->findOrAddSubmetricOfType<MapPrimitiviser_Metrics::Metric_primitiviseAllMapObjects>().get() : nullptr);
    }
    else if (owner->mode == MapPrimitivesProvider::Mode::AllObjectsWithPolygonFiltering)
    {
        primitivisedObjects = owner->primitiviser->primitiviseAllMapObjects(
            Utilities::getScaleDivisor31ToPixel(PointI(owner->tileSize, owner->tileSize), zoom),
            zoom,
            dataTile->mapObjects,
            //NOTE: So far it's safe to turn off this cache. But it has to be rewritten. Since lock/unlock occurs too often, this kills entire performance
            //NOTE: Maybe a QuadTree-based cache with leaf-only locking will save up much. Or use supernodes, like DataBlock
            nullptr, //_primitiviserCache,
            nullptr,
            metric ? metric->findOrAddSubmetricOfType<MapPrimitiviser_Metrics::Metric_primitiviseAllMapObjects>().get() : nullptr);
    }
    else if (owner->mode == MapPrimitivesProvider::Mode::WithoutSurface)
    {
        primitivisedObjects = owner->primitiviser->primitiviseWithoutSurface(
            Utilities::getScaleDivisor31ToPixel(PointI(owner->tileSize, owner->tileSize), zoom),
            zoom,
            dataTile->mapObjects,
            //NOTE: So far it's safe to turn off this cache. But it has to be rewritten. Since lock/unlock occurs too often, this kills entire performance
            //NOTE: Maybe a QuadTree-based cache with leaf-only locking will save up much. Or use supernodes, like DataBlock
            nullptr, //_primitiviserCache,
            nullptr,
            metric ? metric->findOrAddSubmetricOfType<MapPrimitiviser_Metrics::Metric_primitiviseWithoutSurface>().get() : nullptr);
    }
    else // if (owner->mode == MapPrimitivesProvider::Mode::WithSurface)
    {
        const auto tileBBox31 = Utilities::tileBoundingBox31(tileId, zoom);
        primitivisedObjects = owner->primitiviser->primitiviseWithSurface(
            tileBBox31,
            PointI(owner->tileSize, owner->tileSize),
            zoom,
            dataTile->tileSurfaceType,
            dataTile->mapObjects,
            //NOTE: So far it's safe to turn off this cache. But it has to be rewritten. Since lock/unlock occurs too often, this kills entire performance
            //NOTE: Maybe a QuadTree-based cache with leaf-only locking will save up much. Or use supernodes, like DataBlock
            nullptr, //_primitiviserCache,
            nullptr,
            metric ? metric->findOrAddSubmetricOfType<MapPrimitiviser_Metrics::Metric_primitiviseWithSurface>().get() : nullptr);
    }

    // Create tile
    const std::shared_ptr<MapPrimitivesProvider::Data> newTiledData(new MapPrimitivesProvider::Data(
        tileId,
        zoom,
        dataTile,
        primitivisedObjects,
        new RetainableCacheMetadata(tileEntry, dataTile->retainableCacheMetadata)));

    // Publish new tile
    outTiledData = newTiledData;

    // Store weak reference to new tile and mark it as 'Loaded'
    tileEntry->dataIsPresent = true;
    tileEntry->dataWeakRef = newTiledData;
    tileEntry->setState(TileState::Loaded);

    // Notify that tile has been loaded
    {
        QWriteLocker scopedLcoker(&tileEntry->loadedConditionLock);
        tileEntry->loadedCondition.wakeAll();
    }

    if (metric)
        metric->elapsedTime = totalStopwatch.elapsed();

#if OSMAND_PERFORMANCE_METRICS
#if OSMAND_PERFORMANCE_METRICS <= 1
    LogPrintf(LogSeverityLevel::Info,
        "%d polygons, %d polylines, %d points primitivised from %dx%d@%d in %fs",
        primitivisedObjects->polygons.size(),
        primitivisedObjects->polylines.size(),
        primitivisedObjects->polygons.size(),
        tileId.x,
        tileId.y,
        zoom,
        totalStopwatch.elapsed());
#else
    LogPrintf(LogSeverityLevel::Info,
        "%d polygons, %d polylines, %d points primitivised from %dx%d@%d in %fs:\n%s",
        primitivisedObjects->polygons.size(),
        primitivisedObjects->polylines.size(),
        primitivisedObjects->polygons.size(),
        tileId.x,
        tileId.y,
        zoom,
        totalStopwatch.elapsed(),
        qPrintable(metric ? metric->toString(QLatin1String("\t - ")) : QLatin1String("(null)")));
#endif // OSMAND_PERFORMANCE_METRICS <= 1
#endif // OSMAND_PERFORMANCE_METRICS
    
    return true;
}
bool OsmAnd::BinaryMapDataProvider_P::obtainData(
    const TileId tileId,
    const ZoomLevel zoom,
    std::shared_ptr<MapTiledData>& outTiledData,
    const IQueryController* const queryController)
{
    std::shared_ptr<TileEntry> tileEntry;

    for (;;)
    {
        // Try to obtain previous instance of tile
        _tileReferences.obtainOrAllocateEntry(tileEntry, tileId, zoom,
            []
            (const TiledEntriesCollection<TileEntry>& collection, const TileId tileId, const ZoomLevel zoom) -> TileEntry*
            {
                return new TileEntry(collection, tileId, zoom);
            });

        // If state is "Undefined", change it to "Loading" and proceed with loading
        if (tileEntry->setStateIf(TileState::Undefined, TileState::Loading))
            break;

        // In case tile entry is being loaded, wait until it will finish loading
        if (tileEntry->getState() == TileState::Loading)
        {
            QReadLocker scopedLcoker(&tileEntry->_loadedConditionLock);

            // If tile is in 'Loading' state, wait until it will become 'Loaded'
            while (tileEntry->getState() != TileState::Loaded)
                REPEAT_UNTIL(tileEntry->_loadedCondition.wait(&tileEntry->_loadedConditionLock));
        }

        // Try to lock tile reference
        outTiledData = tileEntry->_tile.lock();

        // If successfully locked, just return it
        if (outTiledData)
            return true;

        // Otherwise consider this tile entry as expired, remove it from collection (it's safe to do that right now)
        // This will enable creation of new entry on next loop cycle
        _tileReferences.removeEntry(tileId, zoom);
        tileEntry.reset();
    }

#if OSMAND_PERFORMANCE_METRICS
    const auto total_Begin = std::chrono::high_resolution_clock::now();
#endif // OSMAND_PERFORMANCE_METRICS

    // Obtain OBF data interface
#if OSMAND_PERFORMANCE_METRICS
    const auto obtainDataInterface_Begin = std::chrono::high_resolution_clock::now();
#endif // OSMAND_PERFORMANCE_METRICS
    const auto& dataInterface = owner->obfsCollection->obtainDataInterface();
#if OSMAND_PERFORMANCE_METRICS
    const auto obtainDataInterface_End = std::chrono::high_resolution_clock::now();
    const std::chrono::duration<float> obtainDataInterface_Elapsed = obtainDataInterface_End - obtainDataInterface_Begin;
#endif // OSMAND_PERFORMANCE_METRICS

    // Get bounding box that covers this tile
    const auto tileBBox31 = Utilities::tileBoundingBox31(tileId, zoom);

    // Perform read-out
    QList< std::shared_ptr<const Model::BinaryMapObject> > referencedMapObjects;
    QList< proper::shared_future< std::shared_ptr<const Model::BinaryMapObject> > > futureReferencedMapObjects;
    QList< std::shared_ptr<const Model::BinaryMapObject> > loadedMapObjects;
    QSet< uint64_t > loadedSharedMapObjects;
    MapFoundationType tileFoundation;
#if OSMAND_PERFORMANCE_METRICS
    float dataFilter = 0.0f;
    const auto dataRead_Begin = std::chrono::high_resolution_clock::now();
#endif // OSMAND_PERFORMANCE_METRICS
#if OSMAND_PERFORMANCE_METRICS > 1
    ObfMapSectionReader_Metrics::Metric_loadMapObjects dataRead_Metric;
#endif // OSMAND_PERFORMANCE_METRICS > 1
    dataInterface->loadMapObjects(&loadedMapObjects, &tileFoundation, tileBBox31, zoom, nullptr,
        [this, zoom, &referencedMapObjects, &futureReferencedMapObjects, &loadedSharedMapObjects, tileBBox31
#if OSMAND_PERFORMANCE_METRICS
        , &dataFilter
#endif // OSMAND_PERFORMANCE_METRICS
        ]
    (const std::shared_ptr<const ObfMapSectionInfo>& section, const uint64_t id, const AreaI& bbox, const ZoomLevel firstZoomLevel, const ZoomLevel lastZoomLevel) -> bool
    {
#if OSMAND_PERFORMANCE_METRICS
        const auto dataFilter_Begin = std::chrono::high_resolution_clock::now();
#endif // OSMAND_PERFORMANCE_METRICS

        // This map object may be shared only in case it crosses bounds of a tile
        const auto canNotBeShared = tileBBox31.contains(bbox);

        // If map object can not be shared, just read it
        if (canNotBeShared)
        {
#if OSMAND_PERFORMANCE_METRICS
            const auto dataFilter_End = std::chrono::high_resolution_clock::now();
            const std::chrono::duration<float> dataRead_Elapsed = dataFilter_End - dataFilter_Begin;
            dataFilter += dataRead_Elapsed.count();
#endif // OSMAND_PERFORMANCE_METRICS

            return true;
        }

        // Otherwise, this map object can be shared, so it should be checked for
        // being present in shared mapObjects storage, or be reserved there
        std::shared_ptr<const Model::BinaryMapObject> sharedMapObjectReference;
        proper::shared_future< std::shared_ptr<const Model::BinaryMapObject> > futureSharedMapObjectReference;
        if (_sharedMapObjects.obtainReferenceOrFutureReferenceOrMakePromise(id, zoom, Utilities::enumerateZoomLevels(firstZoomLevel, lastZoomLevel), sharedMapObjectReference, futureSharedMapObjectReference))
        {
            if (sharedMapObjectReference)
            {
                // If map object is already in shared objects cache and is available, use that one
                referencedMapObjects.push_back(qMove(sharedMapObjectReference));
            }
            else
            {
                futureReferencedMapObjects.push_back(qMove(futureSharedMapObjectReference));
            }

#if OSMAND_PERFORMANCE_METRICS
            const auto dataFilter_End = std::chrono::high_resolution_clock::now();
            const std::chrono::duration<float> dataRead_Elapsed = dataFilter_End - dataFilter_Begin;
            dataFilter += dataRead_Elapsed.count();
#endif // OSMAND_PERFORMANCE_METRICS
            return false;
        }

        // This map object was reserved, and is going to be shared, but needs to be loaded
        loadedSharedMapObjects.insert(id);
        return true;
    },
#if OSMAND_PERFORMANCE_METRICS > 1
        & dataRead_Metric
#else
        nullptr
#endif // OSMAND_PERFORMANCE_METRICS > 1
        );

#if OSMAND_PERFORMANCE_METRICS
    const auto dataRead_End = std::chrono::high_resolution_clock::now();
    const std::chrono::duration<float> dataRead_Elapsed = dataRead_End - dataRead_Begin;

    const auto dataIdsProcess_Begin = std::chrono::high_resolution_clock::now();
#endif // OSMAND_PERFORMANCE_METRICS

    // Process loaded-and-shared map objects
    for (auto& mapObject : loadedMapObjects)
    {
        // Check if this map object is shared
        if (!loadedSharedMapObjects.contains(mapObject->id))
            continue;

        // Add unique map object under lock to all zoom levels, for which this map object is valid
        assert(mapObject->level);
        _sharedMapObjects.fulfilPromiseAndReference(
            mapObject->id,
            Utilities::enumerateZoomLevels(mapObject->level->minZoom, mapObject->level->maxZoom),
            mapObject);
    }

    for (auto& futureMapObject : futureReferencedMapObjects)
    {
        auto mapObject = futureMapObject.get();

        referencedMapObjects.push_back(qMove(mapObject));
    }

#if OSMAND_PERFORMANCE_METRICS
    const auto dataIdsProcess_End = std::chrono::high_resolution_clock::now();
    const std::chrono::duration<float> dataIdsProcess_Elapsed = dataIdsProcess_End - dataIdsProcess_Begin;

    const auto dataProcess_Begin = std::chrono::high_resolution_clock::now();
#endif // OSMAND_PERFORMANCE_METRICS
#if OSMAND_PERFORMANCE_METRICS > 1
    Rasterizer_Metrics::Metric_prepareContext dataProcess_metric;
#endif // OSMAND_PERFORMANCE_METRICS > 1

    // Prepare data for the tile
    const auto sharedMapObjectsCount = referencedMapObjects.size() + loadedSharedMapObjects.size();
    const auto allMapObjects = loadedMapObjects + referencedMapObjects;

    // Allocate and prepare rasterizer context
    bool nothingToRasterize = false;
    std::shared_ptr<RasterizerContext> rasterizerContext(new RasterizerContext(owner->rasterizerEnvironment, owner->rasterizerSharedContext));
    Rasterizer::prepareContext(*rasterizerContext, tileBBox31, zoom, tileFoundation, allMapObjects, &nothingToRasterize, nullptr,
#if OSMAND_PERFORMANCE_METRICS > 1
        & dataProcess_metric
#else
        nullptr
#endif // OSMAND_PERFORMANCE_METRICS > 1
        );

#if OSMAND_PERFORMANCE_METRICS
    const auto dataProcess_End = std::chrono::high_resolution_clock::now();
    const std::chrono::duration<float> dataProcess_Elapsed = dataProcess_End - dataProcess_Begin;
#endif // OSMAND_PERFORMANCE_METRICS

    // Create tile
    const std::shared_ptr<BinaryMapDataTile> newTile(new BinaryMapDataTile(
        tileFoundation,
        allMapObjects,
        rasterizerContext,
        nothingToRasterize,
        tileId,
        zoom));
    newTile->_p->_weakLink = _link->getWeak();
    newTile->_p->_refEntry = tileEntry;

    // Publish new tile
    outTiledData = newTile;

    // Store weak reference to new tile and mark it as 'Loaded'
    tileEntry->_tile = newTile;
    tileEntry->setState(TileState::Loaded);

    // Notify that tile has been loaded
    {
        QWriteLocker scopedLcoker(&tileEntry->_loadedConditionLock);
        tileEntry->_loadedCondition.wakeAll();
    }

#if OSMAND_PERFORMANCE_METRICS
    const auto total_End = std::chrono::high_resolution_clock::now();
    const std::chrono::duration<float> total_Elapsed = total_End - total_Begin;
#if OSMAND_PERFORMANCE_METRICS <= 1
    LogPrintf(LogSeverityLevel::Info,
        "%d map objects (%d unique, %d shared) from %dx%d@%d in %fs",
        allMapObjects.size(), allMapObjects.size() - sharedMapObjectsCount, sharedMapObjectsCount,
        tileId.x, tileId.y, zoom,
        total_Elapsed.count());
#else
    LogPrintf(LogSeverityLevel::Info,
        "%d map objects (%d unique, %d shared) from %dx%d@%d in %fs:\n"
        "\topen %fs\n"
        "\tread %fs (filter-by-id %fs):\n"
        "\t - visitedLevels = %d\n"
        "\t - acceptedLevels = %d\n"
        "\t - visitedNodes = %d\n"
        "\t - acceptedNodes = %d\n"
        "\t - elapsedTimeForNodes = %fs\n"
        "\t - mapObjectsBlocksRead = %d\n"
        "\t - visitedMapObjects = %d\n"
        "\t - acceptedMapObjects = %d\n"
        "\t - elapsedTimeForMapObjectsBlocks = %fs\n"
        "\t - elapsedTimeForOnlyVisitedMapObjects = %fs\n"
        "\t - elapsedTimeForOnlyAcceptedMapObjects = %fs\n"
        "\t - average time per 1K only-visited map objects = %fms\n"
        "\t - average time per 1K only-accepted map objects = %fms\n"
        "\tprocess-ids %fs\n"
        "\tprocess-content %fs:\n"
        "\t - elapsedTimeForSortingObjects = %fs\n"
        "\t - elapsedTimeForPolygonizingCoastlines = %fs\n"
        "\t - polygonizedCoastlines = %d\n"
        "\t - elapsedTimeForObtainingPrimitives = %fs\n"
        "\t - elapsedTimeForOrderEvaluation = %fs\n"
        "\t - orderEvaluations = %d\n"
        "\t - average time per 1K order evaluations = %fms\n"
        "\t - elapsedTimeForPolygonEvaluation = %fs\n"
        "\t - polygonEvaluations = %d\n"
        "\t - average time per 1K polygon evaluations = %fms\n"
        "\t - polygonPrimitives = %d\n"
        "\t - elapsedTimeForPolylineEvaluation = %fs\n"
        "\t - polylineEvaluations = %d\n"
        "\t - average time per 1K polyline evaluations = %fms\n"
        "\t - polylinePrimitives = %d\n"
        "\t - elapsedTimeForPointEvaluation = %fs\n"
        "\t - pointEvaluations = %d\n"
        "\t - average time per 1K point evaluations = %fms\n"
        "\t - pointPrimitives = %d\n"
        "\t - elapsedTimeForObtainingPrimitivesSymbols = %fs",
        allMapObjects.size(), allMapObjects.size() - sharedMapObjectsCount, sharedMapObjectsCount,
        tileId.x, tileId.y, zoom,
        total_Elapsed.count(),
        obtainDataInterface_Elapsed.count(),
        dataRead_Elapsed.count(), dataFilter,
        dataRead_Metric.visitedLevels,
        dataRead_Metric.acceptedLevels,
        dataRead_Metric.visitedNodes,
        dataRead_Metric.acceptedNodes,
        dataRead_Metric.elapsedTimeForNodes,
        dataRead_Metric.mapObjectsBlocksRead,
        dataRead_Metric.visitedMapObjects,
        dataRead_Metric.acceptedMapObjects,
        dataRead_Metric.elapsedTimeForMapObjectsBlocks,
        dataRead_Metric.elapsedTimeForOnlyVisitedMapObjects,
        dataRead_Metric.elapsedTimeForOnlyAcceptedMapObjects,
        (dataRead_Metric.elapsedTimeForOnlyVisitedMapObjects * 1000.0f) / (static_cast<float>(dataRead_Metric.visitedMapObjects - dataRead_Metric.acceptedMapObjects) / 1000.0f),
        (dataRead_Metric.elapsedTimeForOnlyAcceptedMapObjects * 1000.0f) / (static_cast<float>(dataRead_Metric.acceptedMapObjects) / 1000.0f),
        dataIdsProcess_Elapsed.count(),
        dataProcess_Elapsed.count(),
        dataProcess_metric.elapsedTimeForSortingObjects,
        dataProcess_metric.elapsedTimeForPolygonizingCoastlines,
        dataProcess_metric.polygonizedCoastlines,
        dataProcess_metric.elapsedTimeForObtainingPrimitives,
        dataProcess_metric.elapsedTimeForOrderEvaluation,
        dataProcess_metric.orderEvaluations,
        (dataProcess_metric.elapsedTimeForOrderEvaluation * 1000.0f / static_cast<float>(dataProcess_metric.orderEvaluations)) * 1000.0f,
        dataProcess_metric.elapsedTimeForPolygonEvaluation,
        dataProcess_metric.polygonEvaluations,
        (dataProcess_metric.elapsedTimeForPolygonEvaluation * 1000.0f / static_cast<float>(dataProcess_metric.polygonEvaluations)) * 1000.0f,
        dataProcess_metric.polygonPrimitives,
        dataProcess_metric.elapsedTimeForPolylineEvaluation,
        dataProcess_metric.polylineEvaluations,
        (dataProcess_metric.elapsedTimeForPolylineEvaluation * 1000.0f / static_cast<float>(dataProcess_metric.polylineEvaluations)) * 1000.0f,
        dataProcess_metric.polylinePrimitives,
        dataProcess_metric.elapsedTimeForPointEvaluation,
        dataProcess_metric.pointEvaluations,
        (dataProcess_metric.elapsedTimeForPointEvaluation * 1000.0f / static_cast<float>(dataProcess_metric.pointEvaluations)) * 1000.0f,
        dataProcess_metric.pointPrimitives,
        dataProcess_metric.elapsedTimeForObtainingPrimitivesSymbols);
#endif // OSMAND_PERFORMANCE_METRICS <= 1
#endif // OSMAND_PERFORMANCE_METRICS

    return true;
}
bool OsmAnd::BinaryMapDataProvider_P::obtainData(
    const TileId tileId,
    const ZoomLevel zoom,
    std::shared_ptr<MapTiledData>& outTiledData,
    BinaryMapDataProvider_Metrics::Metric_obtainData* const metric_,
    const IQueryController* const queryController)
{
#if OSMAND_PERFORMANCE_METRICS
    BinaryMapDataProvider_Metrics::Metric_obtainData localMetric;
    const auto metric = metric_ ? metric_ : &localMetric;
#else
    const auto metric = metric_;
#endif

    std::shared_ptr<TileEntry> tileEntry;

    for (;;)
    {
        // Try to obtain previous instance of tile
        _tileReferences.obtainOrAllocateEntry(tileEntry, tileId, zoom,
            []
            (const TiledEntriesCollection<TileEntry>& collection, const TileId tileId, const ZoomLevel zoom) -> TileEntry*
            {
                return new TileEntry(collection, tileId, zoom);
            });

        // If state is "Undefined", change it to "Loading" and proceed with loading
        if (tileEntry->setStateIf(TileState::Undefined, TileState::Loading))
            break;

        // In case tile entry is being loaded, wait until it will finish loading
        if (tileEntry->getState() == TileState::Loading)
        {
            QReadLocker scopedLcoker(&tileEntry->_loadedConditionLock);

            // If tile is in 'Loading' state, wait until it will become 'Loaded'
            while (tileEntry->getState() != TileState::Loaded)
                REPEAT_UNTIL(tileEntry->_loadedCondition.wait(&tileEntry->_loadedConditionLock));
        }

        // Try to lock tile reference
        outTiledData = tileEntry->_tile.lock();

        // If successfully locked, just return it
        if (outTiledData)
            return true;

        // Otherwise consider this tile entry as expired, remove it from collection (it's safe to do that right now)
        // This will enable creation of new entry on next loop cycle
        _tileReferences.removeEntry(tileId, zoom);
        tileEntry.reset();
    }

    const Stopwatch totalTimeStopwatch(
#if OSMAND_PERFORMANCE_METRICS
        true
#else
        metric != nullptr
#endif // OSMAND_PERFORMANCE_METRICS
        );

    // Obtain OBF data interface
    const Stopwatch obtainObfInterfaceStopwatch(metric != nullptr);
    const auto& dataInterface = owner->obfsCollection->obtainDataInterface();
    if (metric)
        metric->elapsedTimeForObtainingObfInterface += obtainObfInterfaceStopwatch.elapsed();

    // Get bounding box that covers this tile
    const auto tileBBox31 = Utilities::tileBoundingBox31(tileId, zoom);

    // Perform read-out
    const Stopwatch totalReadTimeStopwatch(metric != nullptr);
    QList< std::shared_ptr< const ObfMapSectionReader::DataBlock > > referencedMapDataBlocks;
    QList< std::shared_ptr<const Model::BinaryMapObject> > referencedMapObjects;
    QList< proper::shared_future< std::shared_ptr<const Model::BinaryMapObject> > > futureReferencedMapObjects;
    QList< std::shared_ptr<const Model::BinaryMapObject> > loadedMapObjects;
    QSet< uint64_t > loadedSharedMapObjects;
    auto tileFoundation = MapFoundationType::Undefined;
    dataInterface->loadMapObjects(
        &loadedMapObjects,
        &tileFoundation,
        zoom,
        &tileBBox31,
        [this, zoom, &referencedMapObjects, &futureReferencedMapObjects, &loadedSharedMapObjects, tileBBox31, metric]
        (const std::shared_ptr<const ObfMapSectionInfo>& section, const uint64_t id, const AreaI& bbox, const ZoomLevel firstZoomLevel, const ZoomLevel lastZoomLevel) -> bool
        {
            const Stopwatch objectsFilteringStopwatch(metric != nullptr);

            // This map object may be shared only in case it crosses bounds of a tile
            const auto canNotBeShared = tileBBox31.contains(bbox);

            // If map object can not be shared, just read it
            if (canNotBeShared)
            {
                if (metric)
                    metric->elapsedTimeForObjectsFiltering += objectsFilteringStopwatch.elapsed();

                return true;
            }

            // Otherwise, this map object can be shared, so it should be checked for
            // being present in shared mapObjects storage, or be reserved there
            std::shared_ptr<const Model::BinaryMapObject> sharedMapObjectReference;
            proper::shared_future< std::shared_ptr<const Model::BinaryMapObject> > futureSharedMapObjectReference;
            if (_sharedMapObjects.obtainReferenceOrFutureReferenceOrMakePromise(id, zoom, Utilities::enumerateZoomLevels(firstZoomLevel, lastZoomLevel), sharedMapObjectReference, futureSharedMapObjectReference))
            {
                if (sharedMapObjectReference)
                {
                    // If map object is already in shared objects cache and is available, use that one
                    referencedMapObjects.push_back(qMove(sharedMapObjectReference));
                }
                else
                {
                    futureReferencedMapObjects.push_back(qMove(futureSharedMapObjectReference));
                }

                if (metric)
                    metric->elapsedTimeForObjectsFiltering += objectsFilteringStopwatch.elapsed();

                return false;
            }

            // This map object was reserved, and is going to be shared, but needs to be loaded
            loadedSharedMapObjects.insert(id);
            return true;
        },
        _dataBlocksCache.get(),
        &referencedMapDataBlocks,
        nullptr,// query controller
        metric ? &metric->loadMapObjectsMetric : nullptr);

    // Process loaded-and-shared map objects
    for (auto& mapObject : loadedMapObjects)
    {
        // Check if this map object is shared
        if (!loadedSharedMapObjects.contains(mapObject->id))
            continue;

        // Add unique map object under lock to all zoom levels, for which this map object is valid
        assert(mapObject->level);
        _sharedMapObjects.fulfilPromiseAndReference(
            mapObject->id,
            Utilities::enumerateZoomLevels(mapObject->level->minZoom, mapObject->level->maxZoom),
            mapObject);
    }

    for (auto& futureMapObject : futureReferencedMapObjects)
    {
        auto mapObject = futureMapObject.get();

        referencedMapObjects.push_back(qMove(mapObject));
    }

    if (metric)
        metric->elapsedTimeForRead += totalReadTimeStopwatch.elapsed();

    // Prepare data for the tile
    const auto sharedMapObjectsCount = referencedMapObjects.size() + loadedSharedMapObjects.size();
    const auto allMapObjects = loadedMapObjects + referencedMapObjects;

    // Create tile
    const std::shared_ptr<BinaryMapDataTile> newTile(new BinaryMapDataTile(
        _dataBlocksCache,
        referencedMapDataBlocks,
        tileFoundation,
        allMapObjects,
        tileId,
        zoom));
    newTile->_p->_weakLink = _link->getWeak();
    newTile->_p->_refEntry = tileEntry;

    // Publish new tile
    outTiledData = newTile;

    // Store weak reference to new tile and mark it as 'Loaded'
    tileEntry->_tile = newTile;
    tileEntry->setState(TileState::Loaded);

    // Notify that tile has been loaded
    {
        QWriteLocker scopedLcoker(&tileEntry->_loadedConditionLock);
        tileEntry->_loadedCondition.wakeAll();
    }

    if (metric)
    {
        metric->elapsedTime += totalTimeStopwatch.elapsed();
        metric->objectsCount += allMapObjects.size();
        metric->uniqueObjectsCount += allMapObjects.size() - sharedMapObjectsCount;
        metric->sharedObjectsCount += sharedMapObjectsCount;
    }

#if OSMAND_PERFORMANCE_METRICS
#if OSMAND_PERFORMANCE_METRICS <= 1
    LogPrintf(LogSeverityLevel::Info,
        "%d map objects (%d unique, %d shared) read from %dx%d@%d in %fs",
        allMapObjects.size(),
        allMapObjects.size() - sharedMapObjectsCount,
        sharedMapObjectsCount,
        tileId.x,
        tileId.y,
        zoom,
        totalTimeStopwatch.elapsed());
#else
    LogPrintf(LogSeverityLevel::Info,
        "%d map objects (%d unique, %d shared) read from %dx%d@%d in %fs:\n%s",
        allMapObjects.size(),
        allMapObjects.size() - sharedMapObjectsCount,
        sharedMapObjectsCount,
        tileId.x,
        tileId.y,
        zoom,
        totalTimeStopwatch.elapsed(),
        qPrintable(metric ? metric->toString(QLatin1String("\t - ")) : QLatin1String("(null)")));
#endif // OSMAND_PERFORMANCE_METRICS <= 1
#endif // OSMAND_PERFORMANCE_METRICS

    return true;
}