Files
MapLibreParticleLab/src/mbgl/tile/tile_loader_impl.hpp

219 lines
7.6 KiB
C++

#pragma once
#include <mbgl/renderer/tile_parameters.hpp>
#include <mbgl/storage/file_source.hpp>
#include <mbgl/tile/tile_loader.hpp>
#include <mbgl/util/async_request.hpp>
#include <mbgl/util/tileset.hpp>
#include <cassert>
namespace mbgl {
inline std::exception_ptr getCantLoadTileError() {
return std::make_exception_ptr(std::runtime_error("Can't load tile."));
}
template <typename T>
TileLoader<T>::TileLoader(T& tile_,
const OverscaledTileID& id,
const TileParameters& parameters,
const Tileset& tileset)
: tile(tile_),
necessity(TileNecessity::Optional),
resource(Resource::tile(tileset.tiles.at(0),
parameters.pixelRatio,
id.canonical.x,
id.canonical.y,
id.canonical.z,
tileset.scheme,
Resource::LoadingMethod::CacheOnly)),
fileSource(parameters.fileSource) {
assert(!request);
shared = std::make_shared<Shared>();
if (!fileSource) {
tile.setError(getCantLoadTileError());
return;
}
if (fileSource->supportsCacheOnlyRequests()) {
// When supported, the first request is always std::optional, even if
// the TileLoader is marked as required. That way, we can let the first
// std::optional request continue to load when the TileLoader is later
// changed from required to std::optional. If we started out with a
// required request, we'd have to cancel everything, including the
// initial std::optional part of the request.
loadFromCache();
} else if (necessity == TileNecessity::Required) {
// When the file source doesn't support cache-only requests, and we definitely
// need this data, we can start out with a network request immediately.
loadFromNetwork();
} else {
// When the FileSource doesn't support cache-only requests, we do
// nothing until the data is definitely required.
}
}
template <typename T>
TileLoader<T>::~TileLoader() {
// Tasks may outlive this object, make sure that they are not
// currently running and do not run if they are still pending.
{
std::unique_lock<std::shared_mutex> lock(shared->requestLock);
shared->aborted = true;
}
// Don't touch `tile` here, we may be called from its destructor
// tile.cancel();
request.reset();
fileSource.reset();
shared.reset();
}
template <typename T>
void TileLoader<T>::setNecessity(TileNecessity newNecessity) {
if (newNecessity != necessity) {
necessity = newNecessity;
if (necessity == TileNecessity::Required) {
makeRequired();
} else {
makeOptional();
}
}
}
template <typename T>
void TileLoader<T>::setUpdateParameters(const TileUpdateParameters& params) {
if (updateParameters != params) {
updateParameters = params;
if (hasPendingNetworkRequest()) {
// Update the pending request.
request.reset();
loadFromNetwork();
}
}
}
template <typename T>
void TileLoader<T>::loadFromCache() {
assert(!request);
if (!fileSource) {
tile.setError(getCantLoadTileError());
return;
}
tile.onTileAction(TileOperation::RequestedFromCache);
resource.loadingMethod = Resource::LoadingMethod::CacheOnly;
request = fileSource->request(resource, [this, shared_{shared}](const Response& res) {
do {
if (shared_->requestLock.try_lock_shared()) {
std::shared_lock<std::shared_mutex> lock(shared_->requestLock, std::adopt_lock);
if (shared_->aborted) return;
request.reset();
tile.setTriedCache();
if (res.error && res.error->reason == Response::Error::Reason::NotFound) {
// When the cache-only request could not be satisfied, don't treat
// it as an error. A cache lookup could still return data, _and_ an
// error, in particular when we were able to find the data, but it
// is expired and the Cache-Control headers indicated that we aren't
// allowed to use expired responses. In this case, we still get the
// data which we can use in our conditional network request.
resource.priorModified = res.modified;
resource.priorExpires = res.expires;
resource.priorEtag = res.etag;
resource.priorData = res.data;
} else {
loadedData(res, Resource::LoadingMethod::CacheOnly);
}
if (necessity == TileNecessity::Required) {
loadFromNetwork();
}
break;
}
} while (!shared_->aborted);
});
}
template <typename T>
void TileLoader<T>::makeRequired() {
if (!request) {
loadFromNetwork();
}
}
template <typename T>
void TileLoader<T>::makeOptional() {
if (hasPendingNetworkRequest()) {
// Abort the current request, but only when we know that we're
// specifically querying for a network resource only.
request.reset();
}
}
template <typename T>
void TileLoader<T>::loadedData(const Response& res, Resource::LoadingMethod method) {
if (res.error && res.error->reason != Response::Error::Reason::NotFound) {
tile.setError(std::make_exception_ptr(std::runtime_error(res.error->message)));
tile.onTileAction(TileOperation::Error);
return;
}
if (method == Resource::LoadingMethod::NetworkOnly) {
tile.onTileAction(TileOperation::LoadFromNetwork);
} else if (method == Resource::LoadingMethod::CacheOnly) {
tile.onTileAction(TileOperation::LoadFromCache);
}
if (res.notModified) {
resource.priorExpires = res.expires;
// Do not notify the tile; when we get this message, it already has the
// current version of the data.
tile.setMetadata(res.modified, res.expires);
} else {
resource.priorModified = res.modified;
resource.priorExpires = res.expires;
resource.priorEtag = res.etag;
tile.setMetadata(res.modified, res.expires);
tile.setData(res.noContent ? nullptr : res.data);
}
}
template <typename T>
void TileLoader<T>::loadFromNetwork() {
assert(!request);
if (!fileSource) {
tile.setError(getCantLoadTileError());
return;
}
tile.onTileAction(TileOperation::RequestedFromNetwork);
// Instead of using Resource::LoadingMethod::All, we're first doing a
// CacheOnly, and then a NetworkOnly request.
resource.loadingMethod = Resource::LoadingMethod::NetworkOnly;
resource.minimumUpdateInterval = updateParameters.minimumUpdateInterval;
resource.storagePolicy = updateParameters.isVolatile ? Resource::StoragePolicy::Volatile
: Resource::StoragePolicy::Permanent;
request = fileSource->request(resource, [this, shared_{shared}](const Response& res) {
do {
if (shared_->requestLock.try_lock_shared()) {
std::shared_lock<std::shared_mutex> lock(shared_->requestLock, std::adopt_lock);
if (shared_->aborted) return;
request.reset();
loadedData(res, Resource::LoadingMethod::NetworkOnly);
break;
}
} while (!shared_->aborted);
});
}
} // namespace mbgl