Cogs.Core
BaseRasterSource.cpp
1#include "BaseRasterSource.h"
2#include "JsonSerialization.h"
3#include "ImageDecoder.h"
4#include "StashService.h"
5
6#include "Context.h"
7
8#include "Services/Services.h"
9#include "Bridge/TerrainFunctions.h"
10
11#include "Foundation/Logging/Logger.h"
12#include "Foundation/Platform/IO.h"
13
14namespace {
15 using namespace Cogs::Core::TerrainProvider;
16 Cogs::Logging::Log logger = Cogs::Logging::getLogger("BaseRasterSource");
17}
18
19bool Cogs::Core::TerrainProvider::BaseConfig::compatible(const BaseConfig* other) const
20{
21 const float epsilon = 1e-7f;
22 if (epsilon < std::abs(extent.min.x - other->extent.min.x) ||
23 epsilon < std::abs(extent.min.y - other->extent.min.y) ||
24 epsilon < std::abs(extent.max.x - other->extent.max.x) ||
25 epsilon < std::abs(extent.max.y - other->extent.max.y))
26 {
27 LOG_ERROR(logger, "Mismatched extents");
28 return false;
29 }
30
31 if (epsilon < std::abs(tiling.size.x - other->tiling.size.x) ||
32 epsilon < std::abs(tiling.size.y - other->tiling.size.y) ||
33 tiling.width != other->tiling.width ||
34 tiling.height != other->tiling.height ||
35 tiling.overlapStart != other->tiling.overlapStart ||
36 tiling.overlapEnd != other->tiling.overlapEnd ||
37 tiling.levels != other->tiling.levels)
38 {
39 LOG_ERROR(logger, "Mismatched tilings");
40 return false;
41 }
42
43 if (coordsys.kind != other->coordsys.kind ||
44 coordsys.id != other->coordsys.id)
45 {
46 LOG_ERROR(logger, "Mismatched coord sys");
47 return false;
48 }
49
50 if (textureFormat != other->textureFormat)
51 {
52 LOG_ERROR(logger, "Mismatched texture formats");
53 return false;
54 }
55
56 if (mimeType != other->mimeType) {
57 LOG_ERROR(logger, "Mismatched mime-types");
58 return false;
59 }
60
61 if (cacheKey != other->cacheKey) {
62 LOG_ERROR(logger, "Mismatched cache keys");
63 return false;
64 }
65
66 return true;
67}
68
69
71{
72public:
73 BaseRasterSource* provider = nullptr;
74 Request* request = nullptr;
75
76 void operator()()
77 {
78 auto* stash = provider->stashService->getStash();
79 assert(stash);
80 initializeSharedContext(provider->context, stash->context);
81
82 MimeType kind = MimeType::None;
83 if (request->failure) {
84
85 Cogs::TileData tileData = {};
86 tileData.format = provider->textureFormat;
87 tileData.width = provider->tiling.width;
88 tileData.height = provider->tiling.height;
89 tileData.imageData = nullptr;
90 tileData.stride = 0;
91 tileData.flags = Cogs::TileDataFlags::IsHeight;
92
93 request->loadCallback(request->loadData,
94 int(request->id.level),
95 int(request->id.i),
96 int(request->id.j),
97 &tileData);
98 {
99 Cogs::LockGuard lock(provider->requests.mutex);
100 provider->requests.inFlight.erase(tileKey(request->id));
101 provider->requests.hasHandleTask.remove(request);
102 provider->requests.store.destroy(request);
103 }
104
105 }
106 else if (provider->cache->getTile(stash->contents, kind, request->id)) {
107
108 Cogs::TileData tileData = {};
109
110 stash->decoder.decode(tileData,
111 stash->contents,
112 kind,
113 provider->tiling.width,
114 provider->tiling.height,
115 provider->noDataValue);
116
117 request->loadCallback(request->loadData,
118 int(request->id.level),
119 int(request->id.i),
120 int(request->id.j),
121 &tileData);
122
123 {
124 Cogs::LockGuard lock(provider->requests.mutex);
125 provider->requests.inFlight.erase(tileKey(request->id));
126 provider->requests.hasHandleTask.remove(request);
127 provider->requests.store.destroy(request);
128 }
129 }
130
131 else {
132 {
133 Cogs::LockGuard guard(provider->requests.mutex);
134 provider->requests.hasHandleTask.remove(request);
135 provider->requests.waitingForProvider.pushBack(request);
136 request->task = NoTask;
137 }
138 provider->requestTile(request);
139 }
140 }
141
142};
143
144
145uint64_t Cogs::Core::TerrainProvider::tileKey(const TileId& id)
146{
147 return (uint64_t(id.level) << 56) | (uint64_t(id.j) << 28) | uint64_t(id.i);
148}
149
150Cogs::Core::TerrainProvider::BaseRasterSource::BaseRasterSource(Context* context) :
151 context(context)
152{
153 stashService = context->services->getService<StashService>();
154 assert(stashService);
155}
156
157
158Cogs::Core::TerrainProvider::BaseRasterSource::~BaseRasterSource()
159{
160 LOG_DEBUG(logger, "Shutting down %zx", size_t(this));
161 setErrorCode(ErrorCode::Done);
162
163 std::vector<Request*> running;
164 {
165 Cogs::LockGuard g(requests.mutex);
166 for (auto * item : requests.hasHandleTask) {
167 running.push_back(static_cast<Request*>(item));
168 }
169 }
170 for (auto& req : running) {
171 context->taskManager->wait(req->task);
172 }
173}
174
175Cogs::Core::TerrainProvider::IRasterSource::ErrorCode Cogs::Core::TerrainProvider::BaseRasterSource::getErrorCode() const
176{
177 Cogs::LockGuard guard(state.mutex);
178 return state.errorCode;
179}
180
181void Cogs::Core::TerrainProvider::BaseRasterSource::setErrorCode(ErrorCode newErrorCode)
182{
183 Cogs::LockGuard guard(state.mutex);
184 if (state.errorCode == ErrorCode::NoError) {
185 state.errorCode = newErrorCode;
186 }
187}
188
189bool Cogs::Core::TerrainProvider::BaseRasterSource::init(const BaseConfig& conf, std::unique_ptr<ICache>&& icache)
190{
191 cache = std::move(icache);
192 assert(cache.get());
193
194 extent = conf.extent;
195 tiling = conf.tiling;
196 coordsys = conf.coordsys;
197 textureFormat = conf.textureFormat;
198 if (textureFormat == TextureFormat::Unknown) {
199 LOG_ERROR(logger, "Unknown texture format");
200 return false;
201 }
202 name = conf.name;
203 noDataValue = conf.noData;
204 if (!std::isfinite(conf.extent.min.x) ||
205 !std::isfinite(conf.extent.min.y) ||
206 !std::isfinite(conf.extent.max.x) ||
207 !std::isfinite(conf.extent.max.y) ||
208 (conf.extent.max.x <= conf.extent.min.x) ||
209 (conf.extent.max.y <= conf.extent.min.y))
210 {
211 LOG_ERROR(logger, "Illegal extent [%f,%f]x[%f,%f]",
212 conf.extent.min.x, conf.extent.min.y,
213 conf.extent.max.x, conf.extent.max.y);
214 return false;
215 }
216
217 if (!std::isfinite(conf.tiling.size.x) ||
218 !std::isfinite(conf.tiling.size.y) ||
219 (conf.tiling.size.x <= 0.0) ||
220 (conf.tiling.size.y <= 0.0) ||
221 (conf.tiling.width == 0) ||
222 (conf.tiling.height == 0) ||
223 (conf.tiling.levels == 0))
224 {
225 LOG_ERROR(logger, "Illegal tiling");
226 return false;
227 }
228
229 return true;
230}
231
232void Cogs::Core::TerrainProvider::BaseRasterSource::getConfig(BaseConfig& conf) const
233{
234 conf.extent = extent;
235 conf.tiling = tiling;
236 conf.coordsys = coordsys;
237 conf.textureFormat = textureFormat;
238 conf.name = name;
239}
240
241Cogs::RasterSourceParameters Cogs::Core::TerrainProvider::BaseRasterSource::getParameters() const
242{
244
245 assert(tiling.overlapStart == 0 && "nonezero overlapStart not handled");
246 assert(tiling.overlapEnd == 0 && "nonzero overlapEnd not handled");
247
248 params.name = "Foo";// Cogs::Core::Strings::getC(conf.cacheKey);
249 params.id = id();
250 params.format = textureFormat;
251 params.noData = std::numeric_limits<float>::quiet_NaN(); // Appears to be ignored by the terrain engine
252 params.minX = extent.min.x;
253 params.minY = extent.min.y;
254 params.maxX = extent.max.x;
255 params.maxY = extent.max.y;
256 params.numLevels = tiling.levels;
257 params.tileWidth = tiling.width; // FIXME: handle overlapStart and overlapEnd
258 params.tileHeight = tiling.height;
259 params.deltaX = tiling.size.x;
260 params.deltaY = tiling.size.y;
261 params.cacheSize = 128 * 1024 * 1024;
262 params.flags = 0;
263 params.tileRequestCallback = tileRequestCallbackFunc;
264
265 return params;
266}
267
268void Cogs::Core::TerrainProvider::BaseRasterSource::addTile(const Cogs::Memory::MemoryBuffer& contents, MimeType kind, Request* req, StringView debugLog)
269{
270 auto& tm = context->taskManager;
271 cache->storeTile(contents, kind, req->id, debugLog);
272 LOG_DEBUG(logger, "%s: Updated tile %u:%u,%u", Strings::getC(name), req->id.level, req->id.i, req->id.j);
273 {
274 Cogs::LockGuard guard(requests.mutex);
275 requests.waitingForProvider.remove((Request*)req);
276 requests.hasHandleTask.pushBack((Request*)req);
277 req->task = tm->create(tm->ResourceQueue, HandleRequestTask{ this, req });
278 }
279 tm->enqueue(req->task);
280
281 // requests.cv.notify_one();
282}
283
284void Cogs::Core::TerrainProvider::BaseRasterSource::addTileFailure(Request* req, StringView debugLog)
285{
286 auto& tm = context->taskManager;
288 cache->storeTile(empty, MimeType::None, req->id, debugLog);
289 req->failure = true;
290 LOG_DEBUG(logger, "%s: Flagged tile %u:%u,%u as failed", Strings::getC(name), req->id.level, req->id.i, req->id.j);
291 {
292 Cogs::LockGuard guard(requests.mutex);
293 requests.waitingForProvider.remove((Request*)req);
294 requests.hasHandleTask.pushBack((Request*)req);
295 req->task = tm->create(tm->ResourceQueue, HandleRequestTask{ this, req });
296 }
297 tm->enqueue(req->task);
298
299 //requests.cv.notify_one();
300}
301
302bool Cogs::Core::TerrainProvider::BaseRasterSource::tileRequestCallbackFunc(void* tileLoadData, TileLoadCallback tileLoadCallback, int level, int x, int y)
303{
304 // UGLY HACK: The terrain engine does not provide any means to directly
305 // identify which provider a request belongs. Thus, we peek into internal
306 // structures of the terrain engine. The tileLoadData is a pointer to a
307 // structure that holds the closure of this request. The first element
308 // of the closure is the id for the provider that we set, and which we
309 // defined as the address of the provider.
310 struct TerrainEngineRequestClosure
311 {
312 uint64_t providerId = 0;
313 };
314 auto* closure = reinterpret_cast<TerrainEngineRequestClosure*>(tileLoadData);
315 assert(closure);
316 auto* provider = reinterpret_cast<BaseRasterSource*>(closure->providerId);
317 assert(provider);
318
319 auto& tm = provider->context->taskManager;
320
321 if (provider->getErrorCode() == IRasterSource::ErrorCode::NoError) {
322
323 // Sanity check
324 if (0 <= level && 0 <= x && 0 <= y) {
325
326 TileId id{ unsigned(level), unsigned(x), unsigned(y) };
327 auto h = tileKey(id);
328 {
329 Cogs::UniqueLock guard(provider->requests.mutex);
330
331 // Check if we already have a request for this specific tile in flight.
332 if (provider->requests.inFlight.count(h) == 0) {
333
334 // Nope, issue request
335 provider->requests.inFlight.insert(h);
336 auto* request = provider->requests.store.create();
337 request->loadCallback = tileLoadCallback;
338 request->loadData = tileLoadData;
339 request->id = id;
340 request->task = tm->create(tm->ResourceQueue, HandleRequestTask{ provider, request });
341 provider->requests.hasHandleTask.pushBack(request);
342 guard.unlock();
343
344 tm->enqueue(request->task);
345
346 //provider->requests.cv.notify_one();
347
348 return true; // Tell terrain engine to keep this closure around
349 }
350 }
351 }
352 }
353 return false; // Tell terrain engien to release closure
354}
355
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class Services > services
Services.
Definition: Context.h:174
Log implementation class.
Definition: LogManager.h:139
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:24
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
@ IsHeight
Tile contains height data, min/max values should be provided.