Audacity 3.2.0
GraphicsDataCache.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/*!********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 GraphicsDataCache.cpp
7
8 Dmitry Vedenko
9
10**********************************************************************/
11#include "GraphicsDataCache.h"
12
13#include <algorithm>
14#include <cassert>
15#include <type_traits>
16
17#include "ZoomInfo.h"
18
19#include "float_cast.h"
20#include "RoundUpUnsafe.h"
21
22namespace
23{
24
25bool IsSameSample(double sampleRate, double t0, double t1) noexcept
26{
27 const auto firstSample = llrint(t0 * sampleRate);
28 const auto secondSample = llrint(t1 * sampleRate);
29
30 return firstSample == secondSample;
31}
32
33bool IsSamePPS(double sampleRate, double lhs, double rhs)
34{
35 return std::abs(1.0 / lhs - 1.0 / rhs) *
37 (1.0 / sampleRate);
38}
39
42{
43 return lhs.FirstSample == rhs.FirstSample &&
45}
46
49{
51 return lhs.FirstSample < rhs.FirstSample;
52 else
53 return lhs.PixelsPerSecond < rhs.PixelsPerSecond;
54}
55
56template<typename Container>
58 const Container& container, double pixelsPerSecond, double sampleRate)
59{
60 return std::equal_range(
61 container.begin(), container.end(), pixelsPerSecond,
62 [sampleRate](auto lhs, auto rhs)
63 {
64 if constexpr (std::is_arithmetic_v<std::decay_t<decltype(lhs)>>)
65 return !IsSamePPS(sampleRate, lhs, rhs.Key.PixelsPerSecond) &&
66 lhs < rhs.Key.PixelsPerSecond;
67 else
68 return !IsSamePPS(sampleRate, lhs.Key.PixelsPerSecond, rhs) &&
69 lhs.Key.PixelsPerSecond < rhs;
70 });
71}
72} // namespace
73
75{
76 for (auto& item : mLookup)
77 DisposeElement(item.Data);
78
79 mLookup.clear();
80}
81
83{
84 return mScaledSampleRate;
85}
86
88{
89 mMaxWidth = std::max(mMaxWidth, width);
90}
91
93{
94 return mMaxWidth;
95}
96
98 : mScaledSampleRate { sampleRate }
99{
100}
101
103{
104 if (
105 std::abs(mScaledSampleRate - scaledSampleRate) <=
106 std::numeric_limits<double>::epsilon())
107 return;
108
109 mScaledSampleRate = scaledSampleRate;
110 Invalidate();
111}
112
114{
115}
116
118 GraphicsDataCacheElementBase* prevElement)
119{
120}
121
124 const ZoomInfo& zoomInfo, double t0, double t1)
125{
126 if (bool(t0 > t1) || IsSameSample(mScaledSampleRate, t0, t1))
127 return {};
128
129 const double pixelsPerSecond = zoomInfo.GetZoom();
130
131 const int64_t left = zoomInfo.TimeToPosition(t0);
132 const int64_t right = zoomInfo.TimeToPosition(t1) + 1;
133
134 const int64_t width = right - left;
135
136 const int64_t cacheLeft = left / CacheElementWidth;
137 const int64_t cacheRight = right / CacheElementWidth + 1;
138 const int64_t cacheItemsCount = cacheRight - cacheLeft;
139
140 const int64_t cacheLeftColumn = cacheLeft * CacheElementWidth;
141 const int64_t cacheRightColumn = cacheRight * CacheElementWidth;
142
143 const double samplesPerPixel = mScaledSampleRate / pixelsPerSecond;
144
145 UpdateViewportWidth(width);
146
147 mNewLookupItems.clear();
148 mNewLookupItems.reserve(cacheItemsCount);
149
150 const auto ppsMatchRange =
151 GetPPSMatchRange(mLookup, pixelsPerSecond, mScaledSampleRate);
152
153 for (int64_t itemIndex = 0; itemIndex < cacheItemsCount; ++itemIndex)
154 {
155 const int64_t column = cacheLeftColumn + itemIndex * CacheElementWidth;
156
157 const int64_t firstSample =
158 static_cast<int64_t>(column * samplesPerPixel);
159
160 const auto it = std::find_if(
161 ppsMatchRange.first, ppsMatchRange.second,
162 [firstSample](auto element)
163 { return element.Key.FirstSample == firstSample; });
164
165 if (it == ppsMatchRange.second)
166 mNewLookupItems.push_back({ pixelsPerSecond, firstSample });
167 }
168
169 bool needsSmoothing = !mNewLookupItems.empty();
170
172
173 if (!CreateNewItems())
174 {
176 return {};
177 }
178
179 mLookupHelper.reserve(mLookup.size() + mNewLookupItems.size());
180
181 std::merge(
182 mLookup.begin(), mLookup.end(), mNewLookupItems.begin(),
183 mNewLookupItems.end(), std::back_inserter(mLookupHelper),
184 [sampleRate = mScaledSampleRate](auto lhs, auto rhs)
185 { return IsKeyLess(sampleRate, lhs.Key, rhs.Key); });
186
188 mLookupHelper.clear();
189
190 // Find the very first item satisfying the range
191 const GraphicsDataCacheKey firstItemKey {
192 pixelsPerSecond, int64_t(cacheLeftColumn * samplesPerPixel)
193 };
194
195 auto it = FindKey(firstItemKey);
196
197 assert(it != mLookup.end());
198
199 if (it == mLookup.end())
200 return {};
201
202 GraphicsDataCacheElementBase* prevItem = nullptr;
203
204 for (int64_t itemIndex = 0; itemIndex < cacheItemsCount; ++itemIndex)
205 {
206 auto data = it->Data;
207
209 data->AwaitsEviction = false;
210
211 if (!data->IsComplete && data->LastUpdate != mCacheAccessIndex)
212 {
213 if (!UpdateElement(it->Key, *data))
214 return {};
215
216 needsSmoothing = true;
217 }
218
219 if (needsSmoothing)
220 data->Smooth(prevItem);
221
222 prevItem = data;
223
224 ++it;
225 }
226
228
229 it = FindKey(firstItemKey);
230 auto last = it;
231
232 std::advance(last, cacheItemsCount);
233
234 return { it, last,
235 static_cast<size_t>(std::max(int64_t(0), left - cacheLeftColumn)),
236 static_cast<size_t>(
237 std::max(int64_t(0), cacheRightColumn - right)) };
238}
239
242{
243 auto it = FindKey(key);
244
246
247 if (it != mLookup.end())
248 {
249 GraphicsDataCacheElementBase* data = it->Data;
250
251 if (!data->IsComplete && data->LastUpdate != mCacheAccessIndex)
252 {
253 if (!UpdateElement(it->Key, *data))
254 return {};
255 }
256
257 data->Smooth(it == mLookup.begin() ? nullptr : (it - 1)->Data);
258
259 return data;
260 }
261
262 mNewLookupItems.clear();
263 mNewLookupItems.reserve(1);
264
265 mNewLookupItems.push_back({ key, nullptr });
266
267 LookupElement newElement { key, CreateElement(key) };
268
269 if (newElement.Data == nullptr)
270 return nullptr;
271
272 newElement.Data->LastUpdate = mCacheAccessIndex;
273 newElement.Data->LastCacheAccess = mCacheAccessIndex;
274 newElement.Data->AwaitsEviction = false;
275
276 auto insertedPosition = mLookup.insert(
277 std::upper_bound(
278 mLookup.begin(), mLookup.end(), key,
279 [sampleRate = mScaledSampleRate](auto lhs, auto rhs)
280 {
281 if constexpr (std::is_same_v<
282 std::decay_t<decltype(lhs)>, GraphicsDataCacheKey>)
283 return IsKeyLess(sampleRate, lhs, rhs.Key);
284 else
285 return IsKeyLess(sampleRate, lhs.Key, rhs);
286 }),
287 newElement);
288
289 newElement.Data->Smooth(
290 insertedPosition == mLookup.begin() ? nullptr :
291 (insertedPosition - 1)->Data);
292
294
295 return newElement.Data;
296}
297
299{
300 for (auto& item : mNewLookupItems)
301 {
302 item.Data = CreateElement(item.Key);
303
304 if (item.Data == nullptr)
305 return false;
306
307 item.Data->LastUpdate = mCacheAccessIndex;
308 }
309
310 return true;
311}
312
314{
315 std::for_each(
316 mNewLookupItems.begin(), mNewLookupItems.end(),
317 [](auto elem)
318 {
319 if (elem.Data != nullptr)
320 elem.Data->Dispose();
321 });
322}
323
324GraphicsDataCacheBase::Lookup::iterator
326{
327 return std::find_if(
328 mLookup.begin(), mLookup.end(),
329 [sampleRate = mScaledSampleRate, key](auto lhs)
330 { return IsSameKey(sampleRate, lhs.Key, key); });
331}
332
334{
335 const int64_t lookupSize = static_cast<int64_t>(mLookup.size());
336
337 const auto allowedItems =
339
340 const int64_t itemsToEvict = lookupSize - allowedItems;
341
342 if (itemsToEvict <= 0)
343 return;
344
345 if (itemsToEvict == 1)
346 {
347 auto it = std::min_element(
348 mLookup.begin(), mLookup.end(),
349 [](auto lhs, auto rhs)
350 { return lhs.Data->LastCacheAccess < rhs.Data->LastCacheAccess; });
351
352 if (it->Data->LastCacheAccess < mCacheAccessIndex)
353 {
354 DisposeElement(it->Data);
355 mLookup.erase(it);
356 }
357 }
358 else
359 {
360 PerformFullCleanup(lookupSize, itemsToEvict);
361 }
362}
363
365 int64_t currentSize, int64_t itemsToEvict)
366{
367 mLRUHelper.reserve(currentSize);
368
369 for (size_t i = 0; i < currentSize; ++i)
370 mLRUHelper.push_back(i);
371
372 std::make_heap(
373 mLRUHelper.begin(), mLRUHelper.end(),
374 [this](size_t lhs, size_t rhs)
375 {
376 return mLookup[lhs].Data->LastCacheAccess >
377 mLookup[rhs].Data->LastCacheAccess;
378 });
379
380 for (int64_t itemIndex = 0; itemIndex < itemsToEvict; ++itemIndex)
381 {
382 std::pop_heap(mLRUHelper.begin(), mLRUHelper.end());
383
384 const size_t index = mLRUHelper.back();
385 mLRUHelper.pop_back();
386
387 auto data = mLookup[index].Data;
388
389 if (data->LastCacheAccess >= mCacheAccessIndex)
390 break;
391
392 DisposeElement(data);
393 data->AwaitsEviction = true;
394 }
395
396 mLookup.erase(
397 std::remove_if(
398 mLookup.begin(), mLookup.end(),
399 [](auto item) { return item.Data->AwaitsEviction; }),
400 mLookup.end());
401
402 mLRUHelper.clear();
403}
static const AudacityProject::AttachedObjects::RegisteredFactory key
auto RoundUpUnsafe(LType numerator, RType denominator) noexcept
Returns a rounded up integer result for numerator / denominator.
Definition: RoundUpUnsafe.h:21
GraphicsDataCacheBase(double scaledSampleRate)
std::vector< size_t > mLRUHelper
virtual void DisposeElement(GraphicsDataCacheElementBase *element)=0
This method is called, when the cache element should be evicted. Implementation may not deallocate th...
virtual bool UpdateElement(const GraphicsDataCacheKey &key, GraphicsDataCacheElementBase &element)=0
This method is called on all elements matching the request that are not complete (i....
BaseLookupResult PerformBaseLookup(const ZoomInfo &zoomInfo, double t0, double t1)
Perform a lookup inside the cache. This method modifies mLookup and invalidates any previous result.
int64_t GetMaxViewportWidth() const noexcept
void Invalidate()
Invalidate the cache content.
virtual GraphicsDataCacheElementBase * CreateElement(const GraphicsDataCacheKey &key)=0
Create a new Cache element. Implementation is responsible of the lifetime control.
void UpdateViewportWidth(int64_t width) noexcept
void PerformFullCleanup(int64_t currentSize, int64_t itemsToEvict)
static constexpr uint32_t CacheElementWidth
Lookup::iterator FindKey(GraphicsDataCacheKey key)
double GetScaledSampleRate() const noexcept
Returns the sample rate associated with cache.
void SetScaledSampleRate(double scaledSampleRate)
int64 TimeToPosition(double time, int64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ZoomInfo.cpp:44
double GetZoom() const
Definition: ZoomInfo.cpp:75
auto GetPPSMatchRange(const Container &container, double pixelsPerSecond, double sampleRate)
bool IsSameKey(double sampleRate, GraphicsDataCacheKey lhs, GraphicsDataCacheKey rhs)
bool IsSameSample(double sampleRate, double t0, double t1) noexcept
bool IsKeyLess(double sampleRate, GraphicsDataCacheKey lhs, GraphicsDataCacheKey rhs)
bool IsSamePPS(double sampleRate, double lhs, double rhs)
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:628
A result of the cache lookup.
Element of the cache lookup.
A base class for the for cache elements.
virtual void Dispose()
This method is called when the item is evicted from the cache. Default implementation is empty.
uint64_t LastUpdate
The last time the item was updated. If (!IsComplete && LastUpdate < LastCacheAccess) the item will be...
uint64_t LastCacheAccess
Index filled by GraphicsDataCacheBase to implement LRU eviction policy.
bool IsComplete
Cache implementation is responsible to set this flag when all the data of the item is filled.
virtual void Smooth(GraphicsDataCacheElementBase *prevElement)
This method is called during the lookup when new items are inserted. prevElement can be nullptr....
A key into the graphics data cache.
int64_t FirstSample
Level 2 key: first cached sample in the "global" scale.
double PixelsPerSecond
Level 1 key: zoom level.