Cogs.Core
SwathPathResamplingPositions.cpp
1#include <algorithm>
2#include "Components/WindowComponent.h"
3#include "Systems/DataSetSystem.h"
4#include "SwathPathResamplingPositions.h"
5
6#include <iostream>
7#include <sstream>
8//#define VERBOSE_WRAP(a) do {(a);} while(0)
9#define VERBOSE_WRAP(a) do ; while(0)
10
11bool Cogs::Core::EchoSounder::SwathPathResamplingPositions::clear(float spacing, float maxPathUpsample)
12{
13 this->spacing = spacing;
14 this->maxPathUpsample = std::max(1.f, maxPathUpsample);
15 bufferFront = 0;
16 bufferBack = 0;
17
18 auto N = entries.count();
19 entries.clear();
20 if (N != 0) { VERBOSE_WRAP(std::cerr << "Removed all entities [clear]\n"); }
21 return N != 0;
22}
23
24//Remove all endtries older than validTimeStart
25bool Cogs::Core::EchoSounder::SwathPathResamplingPositions::retireEvictedSlices(int64_t validTimeStart)
26{
27 bool rv = false;
28 while (!entries.empty() && (entries.front().bufferTime < validTimeStart)) {
29 VERBOSE_WRAP(std::cerr << "Popped " << entries.frontIndex() << ":" << entries.front().bufferIndex << "[retireEvict]\n");
30 entries.popFront();
31 rv = true;
32 }
33 return rv;
34}
35
36bool Cogs::Core::EchoSounder::SwathPathResamplingPositions::seedIfQueueEmpty(const Config& config, const DataSetBuffers& buffer)
37{
38 if (!entries.empty()) return false;
39
40 //we want to add the newest slice from the buffer.
41 //if we are resampling, we can't add an entry with reference to last slice, since the position
42 //of an entry is defined as a linear combination between the referenced slice and the next one in buffer
43 bool isResampling = spacing != 0.f;
44 float fractional = isResampling ? 1.f : 0.f;
45 auto ix = (buffer.sliceBegin + buffer.sliceCount - (isResampling ? 2 : 1)) % config.capacity;
46
47 entries.pushBack(Entry{ buffer.timestamps[ix], ix, fractional });
48 VERBOSE_WRAP(std::cerr << "pushBack: " << entries.backIndex() << ": ix=" << ix << ", t=" << fractional << "[seed]\n");
49
50 generation++;
51 return true;
52}
53
54//remove entries such that we are not storing more than maxSlices
55bool Cogs::Core::EchoSounder::SwathPathResamplingPositions::trimEntries()
56{
57 bool rv = false;
58 while (maxSlices < entries.count()) {
59 VERBOSE_WRAP(std::cerr << "Popped " << entries.frontIndex() << ":" << entries.front().bufferIndex << " [trimEntries]\n");
60 entries.popFront();
61 rv = true;
62 }
63 return rv;
64}
65
66//checks if we can include entries with newer timestamps than the newest entry in the entries-queue
67bool Cogs::Core::EchoSounder::SwathPathResamplingPositions::includeMostRecentSlices(const Config& config, const DataSetBuffers& buffer)
68{
69 assert(!entries.empty());
70
71 auto sliceHead1 = (buffer.sliceBegin + buffer.sliceCount - 1u) % config.capacity;
72
73 bool rv = false;
74 if (spacing == 0.0f) {
75
76 while (entries.back().bufferIndex != sliceHead1)
77 {
78 auto ix = (entries.back().bufferIndex + 1) % config.capacity;
79 entries.pushBack(Entry{ buffer.timestamps[ix], ix, 0.0f });
80 VERBOSE_WRAP(std::cerr << "pushBack: " << entries.backIndex() << ": ix=" << ix << ", t=0.0 [includeMostRecentSlices]\n");
81 rv = true;
82 }
83
84 }
85 else {
86
87 assert(entries.back().bufferIndex != sliceHead1); // If fails => logical error somewhere.
88
89 while (true) {
90 auto ep = entryPosition(config, buffer, entries.backIndex());
91 auto currPos = ep.first;
92 auto distanceToCover = std::max(ep.second/maxPathUpsample, spacing);
93 for (uint32_t k = 0; k < maxSliceSkip; k++) {
94 auto currIx = (entries.back().bufferIndex + k) % config.capacity;
95 if (currIx == sliceHead1) {
96 return rv; // Nothing to do.
97 }
98 auto nextIx = (entries.back().bufferIndex + k + 1) % config.capacity;
99
100 auto distanceToNextSlice = glm::distance(currPos, buffer.metaPings[nextIx].vesselPositionGlobal);
101 if (distanceToCover < distanceToNextSlice) {
102 //next wanted sample entry lies somewhere between current entry and next slice
103 float fractional;
104 if (entries.back().bufferIndex == currIx) { // We haven't swapped interval, so take previous fractional into account.
105 auto distBetweenSlices = distanceToNextSlice / (1 - entries.back().fractional);
106 fractional = entries.back().fractional + distanceToCover / distBetweenSlices;
107 }
108 else {
109 fractional = distanceToCover / distanceToNextSlice;
110 }
111 assert(fractional - 1e-5f < 1.f);
112 entries.pushBack(Entry{ buffer.timestamps[currIx], currIx, fractional });
113 VERBOSE_WRAP(std::cerr << "pushBack: " << entries.backIndex() << ": ix=" << currIx << ", t=" << fractional << "[includeMostRecentSlices]\n");
114 rv = true;
115 goto done;
116 }
117 distanceToCover -= distanceToNextSlice;
118 } //end for-loop
119 {
120 // We have skipped so many slices that we force a sample point.
121 auto forceIx = (entries.back().bufferIndex + maxSliceSkip - 1) % config.capacity;
122 entries.pushBack(Entry{ buffer.timestamps[forceIx], forceIx, 0.0 });
123 VERBOSE_WRAP(std::cerr << "pushBack: " << entries.backIndex() << ": ix=" << forceIx << ", t=0.f" << "[includeMostRecentSlices, forced]\n");
124 rv = true;
125 }
126
127 done:
128 ;
129 }
130
131 }
132 return rv;
133}
134
135std::pair<glm::vec3, float> Cogs::Core::EchoSounder::SwathPathResamplingPositions::entryPosition(const Config& config, const DataSetBuffers& buffer, uint32_t entryIx)
136{
137 auto bufferIx = entries[entryIx].bufferIndex;
138 const auto & pc = buffer.metaPings[bufferIx].vesselPositionGlobal;
139 const auto & pn = buffer.metaPings[(bufferIx + 1) % config.capacity].vesselPositionGlobal;
140 return std::make_pair(glm::mix(pc, pn, entries[entryIx].fractional), glm::distance(pc, pn));
141}
142
143
144//checks if we can add entries with older timestamps then the oldest entry in the entries-queue. Usually only happens after seeding an empty queue.
145bool Cogs::Core::EchoSounder::SwathPathResamplingPositions::includeLessRecentSlices(const Config& config, const DataSetBuffers& buffer, int64_t validTimeStart)
146{
147 assert(!entries.empty());
148
149 bool rv = false;
150 if (spacing == 0.0f) {
151
152 while ((entries.count() < maxSlices) && (entries.front().bufferIndex != buffer.sliceBegin))
153 {
154 auto ix = (entries.front().bufferIndex - 1u + config.capacity) % config.capacity;
155 if (buffer.timestamps[ix] < validTimeStart) break;
156 entries.pushFront(Entry{ buffer.timestamps[ix], ix, 0.0f });
157 VERBOSE_WRAP(std::cerr << "pushFront: " << entries.frontIndex() << ": ix=" << ix << ", t=0.0 [includeLessRecentSlices]\n");
158 rv = true;
159 }
160
161 }
162 else {
163
164 while (entries.count() < maxSlices) {
165 auto ep = entryPosition(config, buffer, entries.frontIndex());
166 auto currPos = ep.first;
167 auto distanceToCover = std::max(ep.second / maxPathUpsample, spacing);
168 auto currIx = entries.front().bufferIndex;
169 for (uint32_t k = 0; k < maxSliceSkip; k++) {
170 auto nextIx = (entries.front().bufferIndex - k + config.capacity) % config.capacity;
171
172 if (((currIx == buffer.sliceBegin) && (currIx != nextIx)) || (buffer.timestamps[nextIx] < validTimeStart) ) {
173 return rv; // Trying to iterate past the oldest slice boundary of the buffer, give up.
174 }
175
176 auto distanceToNextSlice = glm::distance(currPos, buffer.metaPings[nextIx].vesselPositionGlobal);
177 if (distanceToCover < distanceToNextSlice) { // Sample point is in current interval.
178
179 float fractional;
180 if (nextIx == entries.front().bufferIndex) { // We haven't swapped interval, so take previous fractional into account.
181 auto distBetweenSlices = distanceToNextSlice / (entries.front().fractional);
182 fractional = entries.front().fractional - distanceToCover / distBetweenSlices;
183 }
184 else {
185 fractional = 1.f - distanceToCover / distanceToNextSlice;
186 }
187 assert(fractional - 1e-5 <= 1.f);
188 entries.pushFront(Entry{ buffer.timestamps[nextIx], nextIx, glm::max(0.f, fractional) });
189 VERBOSE_WRAP(std::cerr << "pushFront: " << entries.frontIndex() << ": ix=" << entries.front().bufferIndex << ", t=" << entries.front().fractional << "[includeLessRecentSlices]\n");
190 rv = true;
191 goto done; // Jump out of for-loop and go to next while-iteration.
192 }
193
194 // Skip to next interval
195 currIx = nextIx;
196 distanceToCover -= distanceToNextSlice;
197 currPos = buffer.metaPings[currIx].vesselPositionGlobal;
198 }
199
200 {
201 // Spacing is so large that we have violated max-slices-to-skip, force insert a sample point.
202 auto forceIx = (entries.front().bufferIndex - maxSliceSkip + config.capacity + 1u) % config.capacity;
203 entries.pushFront(Entry{ buffer.timestamps[forceIx], forceIx, 0.0 });
204 VERBOSE_WRAP(std::cerr << "pushFront: " << entries.backIndex() << ": ix=" << forceIx << ", t=0.f" << "[includeLessRecentSlices, forced]\n");
205 rv = true;
206#ifdef _DEBUG
207 // If forceIx is past the oldest slice boundary, the test in the loop should have kicked in.
208 for (uint32_t k = entries.front().bufferIndex; k != forceIx; k = (k - 1u) % config.capacity) {
209 assert(k != buffer.sliceBegin);
210 }
211#endif
212 }
213 done:
214 ;
215 }
216 }
217 VERBOSE_WRAP(std::cerr << "Done includeLessRecentSlices\n");
218 return rv;
219}
220
221void Cogs::Core::EchoSounder::SwathPathResamplingPositions::printBuffer(const Config& config, const DataSetBuffers& buffer)
222{
223 std::stringstream bufferIndexes;
224 std::stringstream bufferTimes;
225 bufferIndexes << buffer.sliceBegin;
226 bufferTimes << buffer.timestamps[buffer.sliceBegin];
227 //verify that slices in the buffer are in correct order
228 auto sliceTimeFirst = buffer.timestamps[buffer.sliceBegin];
229 for (uint32_t i = 1; i < buffer.sliceCount; ++i) {
230 auto index = (buffer.sliceBegin + i) % config.capacity;
231 auto sliceTimeSecond = buffer.timestamps[index];
232 assert(sliceTimeSecond >= sliceTimeFirst);
233 bufferIndexes << " " << index;
234 bufferTimes << " " << buffer.timestamps[index];
235 sliceTimeFirst = sliceTimeSecond;
236 }
237
238 VERBOSE_WRAP(std::cerr << "Buffer indexes " << bufferIndexes.str() << std::endl);
239 VERBOSE_WRAP(std::cerr << "Buffer time " << bufferTimes.str() << std::endl);
240}
241
242
243bool Cogs::Core::EchoSounder::SwathPathResamplingPositions::update(const WindowComponent* winComp,
244 const DataSetComponent* /*dataComp*/,
245 const DataSetData* dataData)
246{
247 const auto & config = dataData->persistent->config;
248 const auto & buffer = dataData->persistent->buffer;
249
250 /* General idea for resampling :
251 Original slices are stored in buffer. This is a ring buffer, so we always need to modulo the index into this
252 buffer with config.capacity.
253 The resampled path is defined by the entries queue. On this queue we can push/pop to both the front and the end. Newest slices are at the end.
254 To avoid duplicating data, an entry is defined by an index into the slice buffer together with
255 a fraction to determine how far this entry is between the slice referenced by the index and the next slice in the buffer.
256 See entryPosition() for how the position of an entry is defined.
257 If we are not resampling (spacing == 0), then we chose one entry with fraction 0 for each slice in the buffer.
258 */
259
260 uint32_t bufferFrontNew = buffer.sliceBegin;
261 uint32_t bufferBackNew = buffer.sliceBegin + buffer.sliceCount;
262 uint32_t dataSetClearGenNew = dataData->clearGen;
263
264 // Check if buffer has changed since last time we inspected it.
265 if ((bufferFrontNew == bufferFront) && (bufferBackNew == bufferBack) && (dataSetClearGenNew == dataSetClearGen)) return false;
266 bufferFront = bufferFrontNew;
267 bufferBack = bufferBackNew;
268
269 bool clearedNonEmpty = false;
270 if (dataSetClearGen != dataSetClearGenNew) {
271 auto count = entries.count();
272 entries.clear();
273 dataSetClearGen = dataSetClearGenNew;
274 VERBOSE_WRAP(std::cerr << "Cleared " << count << " entites due to data set cleared " << dataSetClearGenNew << std::endl);
275 clearedNonEmpty = count != 0;
276 }
277
278 if (buffer.sliceCount < 2) {
279 auto count = entries.count();
280 entries.clear();
281 VERBOSE_WRAP(std::cerr << "Cleared " << count << " entites due to sliceCount " << buffer.sliceCount << std::endl);
282 return count != 0 || clearedNonEmpty;
283 }
284
285#ifdef _DEBUG //Print current state of buffer and check that they are in correct order
286 VERBOSE_WRAP(std::cerr << "start resample update " << std::endl);
287 if (buffer.sliceCount < 50) {
288 printBuffer(config, buffer);
289 }
290 VERBOSE_WRAP(std::cerr << "Num entries " << entries.count() << std::endl);
291#endif
292
293 if (entries.count() > 0) {
294 assert(buffer.timestamps[(buffer.sliceBegin + buffer.sliceCount - 1) % config.capacity] > entries.back().bufferTime && "got older slices than current resample path");
295 }
296
297 // Note: If we start to discard based on winComp->head, we can end up in the situation that
298 // indices change content, and this must be percolated up via generation or something clever.
299 bool rv0 = retireEvictedSlices(std::max(buffer.timestamps[buffer.sliceBegin], winComp->tail));
300 bool rv1 = seedIfQueueEmpty(config, buffer);
301 bool rv2 = includeMostRecentSlices(config, buffer);
302 bool rv3 = false;
303 bool rv4 = false;
304 if (maxSlices < entries.count()) {
305 rv3 = trimEntries();
306 }
307 else if (entries.count() < maxSlices) {
308 rv4 = includeLessRecentSlices(config, buffer, std::max(buffer.timestamps[buffer.sliceBegin], winComp->tail));
309 }
310
311 bool rv = rv0 || rv1 || rv2 || rv3 || rv4;
312#ifdef _DEBUG
313 // Sanity checks
314
315 auto tsBufferBegin = buffer.timestamps[buffer.sliceBegin] - 1;
316 auto tsBufferEnd = buffer.timestamps[(buffer.sliceBegin + buffer.sliceCount - 1) % config.capacity] + 1;
317
318 size_t n = 0;
319
320 VERBOSE_WRAP(std::cerr << "--- SamplingPositions"
321 << " r0=" << rv0
322 << " r1=" << rv1
323 << " r2=" << rv2
324 << " r3=" << rv3
325 << " r4=" << rv4 << "\n");
326
327 std::string entriesStr = "";
328 for (auto i = entries.frontIndex(); i != entries.backIndex() + 1; i++) {
329 entriesStr += "{ix = " + std::to_string(entries[i].bufferIndex) + ", frac = " + std::to_string(entries[i].fractional) + "}";
330 }
331 VERBOSE_WRAP(std::cerr << "Entries = " << entriesStr << std::endl);
332 auto tsPreviousEntry = tsBufferBegin;
333
334 for (auto i = entries.frontIndex(); i != entries.backIndex() + 1; i++) {
335 VERBOSE_WRAP(std::cerr << i << ": {ix=" << entries[i].bufferIndex << ", frac=" << entries[i].fractional
336 <<", ts=" << entries[i].bufferTime << "}\n");
337 auto tsCurrentIndex = buffer.timestamps[entries[i].bufferIndex];
338
339 //tsBufferBgin < tsCurrentEntry < tsBufferEnd
340 assert(tsBufferBegin < tsCurrentIndex);
341 assert(tsCurrentIndex < tsBufferEnd);
342
343 if (spacing == 0.0f) {
344 assert(entries[i].fractional == 0.0f);
345
346 }
347 else {
348 auto tsNextIndex = buffer.timestamps[(entries[i].bufferIndex + 1) % config.capacity];
349 //tsBufferStart < tsNextIndex < tsBufferEnd
350 assert(tsBufferBegin < tsNextIndex);
351 assert(tsNextIndex < tsBufferEnd);
352
353 //time of slices should be strict increasing
354 assert(tsCurrentIndex < tsNextIndex);
355
356 auto tsCurrentEntry = tsCurrentIndex + (int64_t)(entries[i].fractional*(tsNextIndex - tsCurrentIndex));
357
358 assert(tsPreviousEntry <= tsCurrentEntry); //don't have '<' since we are casting to int.
359
360 tsPreviousEntry = tsCurrentEntry;
361 }
362 n++;
363 }
364 assert(n == entries.count());
365 VERBOSE_WRAP(std::cerr << "Done update" << std::endl);
366#endif
367 return rv;
368}