diff --git a/src/mbgl/source/source.cpp b/src/mbgl/source/source.cpp index 8b3e8858f39..f3048e076f9 100644 --- a/src/mbgl/source/source.cpp +++ b/src/mbgl/source/source.cpp @@ -286,6 +286,7 @@ bool Source::update(const StyleUpdateParameters& parameters) { }; auto createTileDataFn = [this, ¶meters](const OverscaledTileID& dataTileID) -> TileData* { if (auto data = createTile(dataTileID, parameters)) { + data->getTileSource()->setNecessity(TileSource::Necessity::Required); return tileDataMap.emplace(dataTileID, std::move(data)).first->second.get(); } else { return nullptr; @@ -315,6 +316,7 @@ bool Source::update(const StyleUpdateParameters& parameters) { auto retainIt = retain.begin(); while (dataIt != tileDataMap.end()) { if (retainIt == retain.end() || dataIt->first < *retainIt) { + dataIt->second->getTileSource()->setNecessity(TileSource::Necessity::Optional); cache.add(dataIt->first, std::move(dataIt->second)); tileDataMap.erase(dataIt++); } else { diff --git a/src/mbgl/tile/file_based_tile_source.hpp b/src/mbgl/tile/file_based_tile_source.hpp index e4099ed3164..72d0782710a 100644 --- a/src/mbgl/tile/file_based_tile_source.hpp +++ b/src/mbgl/tile/file_based_tile_source.hpp @@ -6,6 +6,7 @@ namespace mbgl { class FileSource; class AsyncRequest; +class Response; template class FileBasedTileSource : public T { @@ -14,7 +15,15 @@ class FileBasedTileSource : public T { virtual ~FileBasedTileSource() = default; protected: - const Resource resource; + void makeRequired() override; + void makeOptional() override; + +private: + void loadedData(const Response&); + void loadRequired(); + +private: + Resource resource; FileSource& fileSource; std::unique_ptr request; }; diff --git a/src/mbgl/tile/file_based_tile_source_impl.hpp b/src/mbgl/tile/file_based_tile_source_impl.hpp index 1527f3b13a0..c64789ca3cf 100644 --- a/src/mbgl/tile/file_based_tile_source_impl.hpp +++ b/src/mbgl/tile/file_based_tile_source_impl.hpp @@ -3,6 +3,8 @@ #include #include +#include + namespace mbgl { template @@ -10,18 +12,64 @@ FileBasedTileSource::FileBasedTileSource(typename T::data_type& tileData_, const Resource& resource_, FileSource& fileSource_) : T(tileData_), resource(resource_), fileSource(fileSource_) { + assert(!request); + // The first request is always optional. + resource.necessity = Resource::Optional; request = fileSource.request(resource, [this](Response res) { - if (res.error) { - T::tileData.setError(std::make_exception_ptr(std::runtime_error(res.error->message))); - } else if (res.notModified) { - return; - } else if (res.noContent) { - T::tileData.setData(nullptr, res.modified, res.expires); - } else { - - T::tileData.setData(I::parseData(res.data), res.modified, res.expires); + request.reset(); + loadedData(res); + T::loaded = true; + if (T::isRequired()) { + loadRequired(); } }); } +template +void FileBasedTileSource::makeRequired() { + if (T::loaded && !request) { + loadRequired(); + } +} + +template +void FileBasedTileSource::makeOptional() { + if (T::loaded && request) { + // Abort a potential HTTP request. + request.reset(); + } +} + +template +void FileBasedTileSource::loadedData(const Response& res) { + if (res.error && !T::loaded && res.error->reason == Response::Error::Reason::NotFound) { + // When the optional request could not be satisfied, don't treat it as an error. Instead, + // we make sure that the next request knows that there has been an optional request before + // by setting one of the prior* fields. + resource.priorExpires = Timestamp{ Seconds::zero() }; + } else if (res.error) { + T::tileData.setError(std::make_exception_ptr(std::runtime_error(res.error->message))); + } else if (res.notModified) { + resource.priorExpires = res.expires; + // Do not notify the TileData object; when we get this message, it already has the current + // version of the data. + } else { + resource.priorModified = res.modified; + resource.priorExpires = res.expires; + resource.priorEtag = res.etag; + T::tileData.setData(res.noContent ? nullptr : I::parseData(res.data), res.modified, + res.expires); + } +} + +template +void FileBasedTileSource::loadRequired() { + assert(!request); + + resource.necessity = Resource::Required; + request = fileSource.request(resource, [this](Response res) { + loadedData(res); + }); +} + } // namespace mbgl diff --git a/src/mbgl/tile/tile_data.hpp b/src/mbgl/tile/tile_data.hpp index aac1c66e35c..8c4fbf0ce0f 100644 --- a/src/mbgl/tile/tile_data.hpp +++ b/src/mbgl/tile/tile_data.hpp @@ -30,6 +30,7 @@ class TileData : private util::noncopyable { void setObserver(TileDataObserver* observer); void setTileSource(std::unique_ptr); + TileSource* getTileSource() { return tileSource.get(); } // Mark this tile as no longer needed and cancel any pending work. virtual void cancel() = 0; @@ -60,7 +61,6 @@ class TileData : private util::noncopyable { return availableData == DataAvailability::Some; } - void dumpDebugLogs() const; const OverscaledTileID id; diff --git a/src/mbgl/tile/tile_source.hpp b/src/mbgl/tile/tile_source.hpp index ba4555129d5..230b2593218 100644 --- a/src/mbgl/tile/tile_source.hpp +++ b/src/mbgl/tile/tile_source.hpp @@ -5,8 +5,64 @@ namespace mbgl { class TileSource : private util::noncopyable { +public: + // TileSources can have two states: optional or required. + // - optional means that only low-cost actions should be taken to obtain the data + // (e.g. load from cache, but accept stale data) + // - required means that every effort should be taken to obtain the data (e.g. load + // from internet and keep the data fresh if it expires) + enum class Necessity : bool { + Optional = false, + Required = true, + }; + +protected: + TileSource(Necessity necessity_ = Necessity::Optional) : necessity(necessity_) { + } + public: virtual ~TileSource() = default; + + bool isOptional() const { + return necessity == Necessity::Optional; + } + + bool isRequired() const { + return necessity == Necessity::Required; + } + + void setNecessity(Necessity newNecessity) { + if (newNecessity != necessity) { + necessity = newNecessity; + if (necessity == Necessity::Required) { + makeRequired(); + } else { + makeOptional(); + } + } + } + + bool isLoaded() const { + // "loaded" is considered true once the initial load action completed, regardless of whether + // the TileData is required or optional, and whether any actual TileData was loaded in the + // case of optional TileData + return loaded; + } + +protected: + // called when the tile is one of the ideal tiles that we want to show definitely. the tile source + // should try to make every effort (e.g. fetch from internet, or revalidate existing resources). + virtual void makeRequired() {} + + // called when the zoom level no longer corresponds to the displayed one, but + // we're still interested in retaining the tile, e.g. for backfill. + // subclassed TileSources should cancel actions they are taking to provide + // an up-to-date version or load new data + virtual void makeOptional() {} + +protected: + Necessity necessity; + bool loaded = false; }; class GeometryTileData; @@ -17,7 +73,9 @@ class GeometryTileSource : public TileSource { using data_type = GeometryTileData; protected: - GeometryTileSource(data_type& tileData_) : tileData(tileData_) {} + GeometryTileSource(data_type& tileData_, Necessity necessity_ = Necessity::Optional) + : TileSource(necessity_), tileData(tileData_) { + } public: virtual ~GeometryTileSource() = default; @@ -31,7 +89,8 @@ class RasterTileSource : public TileSource { using data_type = RasterTileData; protected: - RasterTileSource(data_type& tileData_) : tileData(tileData_) {}; + RasterTileSource(data_type& tileData_, Necessity necessity_ = Necessity::Optional) + : TileSource(necessity_), tileData(tileData_){}; public: virtual ~RasterTileSource() = default;