|
25 | 25 | #include <olp/core/client/PendingRequests.h> |
26 | 26 | #include <olp/core/client/TaskContext.h> |
27 | 27 | #include <olp/core/logging/Log.h> |
| 28 | +#include <olp/dataservice/read/CatalogVersionRequest.h> |
| 29 | +#include <olp/dataservice/read/PrefetchTileResult.h> |
28 | 30 |
|
29 | 31 | #include "Common.h" |
30 | 32 | #include "repositories/CatalogRepository.h" |
|
33 | 35 | #include "repositories/ExecuteOrSchedule.inl" |
34 | 36 | #include "repositories/PartitionsCacheRepository.h" |
35 | 37 | #include "repositories/PartitionsRepository.h" |
| 38 | +#include "repositories/PrefetchTilesRepository.h" |
36 | 39 |
|
37 | 40 | namespace olp { |
38 | 41 | namespace dataservice { |
39 | 42 | namespace read { |
40 | 43 |
|
41 | 44 | namespace { |
42 | 45 | constexpr auto kLogTag = "VolatileLayerClientImpl"; |
| 46 | + |
| 47 | +bool IsOnlyInputTiles(const PrefetchTilesRequest& request) { |
| 48 | + return !(request.GetMinLevel() > 0 && |
| 49 | + request.GetMinLevel() < request.GetMaxLevel() && |
| 50 | + request.GetMaxLevel() < geo::TileKey().Level() && |
| 51 | + request.GetMinLevel() < geo::TileKey().Level()); |
| 52 | +} |
43 | 53 | } // namespace |
44 | 54 |
|
45 | 55 | VolatileLayerClientImpl::VolatileLayerClientImpl( |
@@ -149,6 +159,198 @@ bool VolatileLayerClientImpl::RemoveFromCache(const geo::TileKey& tile) { |
149 | 159 | return RemoveFromCache(partition_id); |
150 | 160 | } |
151 | 161 |
|
| 162 | +client::CancellationToken VolatileLayerClientImpl::PrefetchTiles( |
| 163 | + PrefetchTilesRequest request, PrefetchTilesResponseCallback callback) { |
| 164 | + // Used as empty response to be able to execute initial task |
| 165 | + using EmptyResponse = Response<PrefetchTileNoError>; |
| 166 | + using PrefetchResult = PrefetchTilesResult::value_type; |
| 167 | + using PrefetchResultFuture = std::future<PrefetchResult>; |
| 168 | + using PrefetchResultPromise = std::promise<PrefetchResult>; |
| 169 | + using client::CancellationContext; |
| 170 | + using client::ErrorCode; |
| 171 | + |
| 172 | + auto catalog = catalog_; |
| 173 | + auto layer_id = layer_id_; |
| 174 | + auto settings = settings_; |
| 175 | + auto pending_requests = pending_requests_; |
| 176 | + |
| 177 | + auto token = AddTask( |
| 178 | + settings.task_scheduler, pending_requests, |
| 179 | + [=](CancellationContext context) mutable -> EmptyResponse { |
| 180 | + const auto& tile_keys = request.GetTileKeys(); |
| 181 | + if (tile_keys.empty()) { |
| 182 | + OLP_SDK_LOG_WARNING_F(kLogTag, |
| 183 | + "PrefetchTiles : invalid request, layer=%s", |
| 184 | + layer_id.c_str()); |
| 185 | + return {{ErrorCode::InvalidArgument, "Empty tile key list"}}; |
| 186 | + } |
| 187 | + |
| 188 | + const auto key = request.CreateKey(layer_id); |
| 189 | + OLP_SDK_LOG_INFO_F(kLogTag, "PrefetchTiles: using key=%s", key.c_str()); |
| 190 | + |
| 191 | + // Calculate the minimal set of Tile keys and depth to |
| 192 | + // cover tree. |
| 193 | + bool request_only_input_tiles = IsOnlyInputTiles(request); |
| 194 | + unsigned int min_level = |
| 195 | + (request_only_input_tiles ? 0 : request.GetMinLevel()); |
| 196 | + unsigned int max_level = |
| 197 | + (request_only_input_tiles ? 0 : request.GetMaxLevel()); |
| 198 | + |
| 199 | + auto sliced_tiles = repository::PrefetchTilesRepository::GetSlicedTiles( |
| 200 | + tile_keys, min_level, max_level); |
| 201 | + |
| 202 | + if (sliced_tiles.empty()) { |
| 203 | + OLP_SDK_LOG_WARNING_F(kLogTag, |
| 204 | + "PrefetchTiles: tile/level mismatch, key=%s", |
| 205 | + key.c_str()); |
| 206 | + return {{ErrorCode::InvalidArgument, "TileKeys/levels mismatch"}}; |
| 207 | + } |
| 208 | + |
| 209 | + OLP_SDK_LOG_DEBUG_F(kLogTag, "PrefetchTiles, subquads=%zu, key=%s", |
| 210 | + sliced_tiles.size(), key.c_str()); |
| 211 | + |
| 212 | + auto sub_tiles = repository::PrefetchTilesRepository::GetSubTiles( |
| 213 | + catalog, layer_id, request, boost::none, sliced_tiles, context, |
| 214 | + settings); |
| 215 | + |
| 216 | + if (!sub_tiles.IsSuccessful()) { |
| 217 | + return sub_tiles.GetError(); |
| 218 | + } |
| 219 | + |
| 220 | + const auto& tiles_result = sub_tiles.GetResult(); |
| 221 | + if (tiles_result.empty()) { |
| 222 | + OLP_SDK_LOG_WARNING_F( |
| 223 | + kLogTag, "PrefetchTiles: subtiles empty, key=%s", key.c_str()); |
| 224 | + return {{ErrorCode::InvalidArgument, "Subquads retrieval failed"}}; |
| 225 | + } |
| 226 | + |
| 227 | + OLP_SDK_LOG_INFO_F(kLogTag, "Prefetch start, key=%s, tiles=%zu", |
| 228 | + key.c_str(), tiles_result.size()); |
| 229 | + |
| 230 | + // Once we have the data create for each subtile a task and push it |
| 231 | + // onto the TaskScheduler. One additional last task is added which |
| 232 | + // waits for all previous tasks to finish so that it may call the user |
| 233 | + // with the result. |
| 234 | + auto futures = std::make_shared<std::vector<PrefetchResultFuture>>(); |
| 235 | + std::vector<CancellationContext> contexts; |
| 236 | + contexts.reserve(tiles_result.size() + 1u); |
| 237 | + auto it = tiles_result.begin(); |
| 238 | + auto skip_tile = [&](const geo::TileKey& tile_key) { |
| 239 | + if (request_only_input_tiles) { |
| 240 | + return (std::find(tile_keys.begin(), tile_keys.end(), tile_key) == |
| 241 | + tile_keys.end()); |
| 242 | + } |
| 243 | + // skip tiles outside min/max segment |
| 244 | + return (tile_key.Level() < request.GetMinLevel() || |
| 245 | + tile_key.Level() > request.GetMaxLevel()); |
| 246 | + }; |
| 247 | + |
| 248 | + while (!context.IsCancelled() && it != tiles_result.end()) { |
| 249 | + auto const& tile = it->first; |
| 250 | + if (skip_tile(tile)) { |
| 251 | + it++; |
| 252 | + continue; |
| 253 | + } |
| 254 | + |
| 255 | + auto const& handle = it->second; |
| 256 | + auto const& biling_tag = request.GetBillingTag(); |
| 257 | + auto promise = std::make_shared<PrefetchResultPromise>(); |
| 258 | + auto flag = std::make_shared<std::atomic_bool>(false); |
| 259 | + futures->emplace_back(promise->get_future()); |
| 260 | + auto context_it = contexts.emplace(contexts.end()); |
| 261 | + |
| 262 | + AddTask( |
| 263 | + settings.task_scheduler, pending_requests, |
| 264 | + [=](CancellationContext inner_context) { |
| 265 | + auto data = repository::DataRepository::GetVolatileData( |
| 266 | + catalog, layer_id, |
| 267 | + DataRequest().WithDataHandle(handle).WithBillingTag( |
| 268 | + biling_tag), |
| 269 | + inner_context, settings); |
| 270 | + |
| 271 | + if (!data.IsSuccessful()) { |
| 272 | + promise->set_value(std::make_shared<PrefetchTileResult>( |
| 273 | + tile, data.GetError())); |
| 274 | + } else { |
| 275 | + promise->set_value(std::make_shared<PrefetchTileResult>( |
| 276 | + tile, PrefetchTileNoError())); |
| 277 | + } |
| 278 | + |
| 279 | + flag->exchange(true); |
| 280 | + return EmptyResponse(PrefetchTileNoError()); |
| 281 | + }, |
| 282 | + [=](EmptyResponse) { |
| 283 | + if (!flag->load()) { |
| 284 | + // If above task was cancelled we might need to set |
| 285 | + // promise else below task will wait forever |
| 286 | + promise->set_value(std::make_shared<PrefetchTileResult>( |
| 287 | + tile, |
| 288 | + client::ApiError(ErrorCode::Cancelled, "Cancelled"))); |
| 289 | + } |
| 290 | + }, |
| 291 | + *context_it); |
| 292 | + it++; |
| 293 | + } |
| 294 | + |
| 295 | + // Task to wait for previously triggered data download to collect |
| 296 | + // responses and trigger user callback. |
| 297 | + AddTask( |
| 298 | + settings.task_scheduler, pending_requests, |
| 299 | + [=](CancellationContext inner_context) -> PrefetchTilesResponse { |
| 300 | + PrefetchTilesResult result; |
| 301 | + result.reserve(futures->size()); |
| 302 | + |
| 303 | + for (auto& future : *futures) { |
| 304 | + // Check if cancelled in between. |
| 305 | + if (inner_context.IsCancelled()) { |
| 306 | + return {{ErrorCode::Cancelled, "Cancelled"}}; |
| 307 | + } |
| 308 | + |
| 309 | + auto tile_result = future.get(); |
| 310 | + result.emplace_back(std::move(tile_result)); |
| 311 | + } |
| 312 | + |
| 313 | + OLP_SDK_LOG_INFO_F(kLogTag, "Prefetch done, key=%s, tiles=%zu", |
| 314 | + key.c_str(), result.size()); |
| 315 | + return PrefetchTilesResponse(std::move(result)); |
| 316 | + }, |
| 317 | + callback, *contexts.emplace(contexts.end())); |
| 318 | + |
| 319 | + context.ExecuteOrCancelled([&]() { |
| 320 | + return client::CancellationToken([contexts]() { |
| 321 | + for (auto context : contexts) { |
| 322 | + context.CancelOperation(); |
| 323 | + } |
| 324 | + }); |
| 325 | + }); |
| 326 | + |
| 327 | + return EmptyResponse(PrefetchTileNoError()); |
| 328 | + }, |
| 329 | + // Because the handling of prefetch tiles responses is performed by the |
| 330 | + // inner-task, no need to set a callback here. Otherwise, the user would |
| 331 | + // be notified with empty results. |
| 332 | + // It is possible to not invoke inner task, when it was cancelled before |
| 333 | + // execution. |
| 334 | + [callback](EmptyResponse response) { |
| 335 | + // Inner task only generates successfull result |
| 336 | + if (!response.IsSuccessful()) { |
| 337 | + callback(response.GetError()); |
| 338 | + } |
| 339 | + }); |
| 340 | + |
| 341 | + return token; |
| 342 | +} |
| 343 | + |
| 344 | +client::CancellableFuture<PrefetchTilesResponse> |
| 345 | +VolatileLayerClientImpl::PrefetchTiles(PrefetchTilesRequest request) { |
| 346 | + auto promise = std::make_shared<std::promise<PrefetchTilesResponse>>(); |
| 347 | + auto callback = [=](PrefetchTilesResponse resp) { |
| 348 | + promise->set_value(std::move(resp)); |
| 349 | + }; |
| 350 | + auto token = PrefetchTiles(std::move(request), std::move(callback)); |
| 351 | + return client::CancellableFuture<PrefetchTilesResponse>(token, promise); |
| 352 | +} |
| 353 | + |
152 | 354 | } // namespace read |
153 | 355 | } // namespace dataservice |
154 | 356 | } // namespace olp |
0 commit comments