| e8555c89 Andy McCall |
1 |
#ifndef TIMELINE_HH |
| 2 |
#define TIMELINE_HH |
| 3 |
|
| 4 |
#include <algorithm> |
| 5 |
#include <cmath> |
| 6 |
#include <concepts> |
| 7 |
#include <optional> |
| 8 |
#include <span> |
| 9 |
|
| 10 |
namespace openmsx { |
| 11 |
|
| 12 |
|
| 13 |
template<typename T, typename Time> |
| 14 |
concept HasTime = requires(T t) { |
| 15 |
{ t.time } -> std::convertible_to<Time>; |
| 16 |
}; |
| 17 |
|
| 18 |
|
| 19 |
template<typename T, typename Time> |
| 20 |
concept TimelineMapper = requires(T c, Time time, float x) { |
| 21 |
{ c.timeToX(time) } -> std::convertible_to<float>; |
| 22 |
{ c.xToTime(x) } -> std::convertible_to<Time>; |
| 23 |
}; |
| 24 |
|
| 25 |
|
| 26 |
|
| 27 |
|
| 28 |
|
| 29 |
|
| 30 |
|
| 31 |
|
| 32 |
|
| 33 |
|
| 34 |
|
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
|
| 39 |
|
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
|
| 50 |
|
| 51 |
|
| 52 |
|
| 53 |
|
| 54 |
|
| 55 |
|
| 56 |
|
| 57 |
|
| 58 |
|
| 59 |
|
| 60 |
|
| 61 |
|
| 62 |
|
| 63 |
|
| 64 |
|
| 65 |
|
| 66 |
|
| 67 |
|
| 68 |
|
| 69 |
|
| 70 |
|
| 71 |
|
| 72 |
|
| 73 |
|
| 74 |
|
| 75 |
template<typename Time, HasTime<Time> Event, TimelineMapper<Time> Mapper> |
| 76 |
void processTimeline( |
| 77 |
std::span<const Event> events, |
| 78 |
Time endTime, |
| 79 |
const Mapper& mapper, |
| 80 |
std::invocable<float, float, const Event&> auto onDetailed, |
| 81 |
std::invocable<float, float, typename std::span<const Event>::iterator, typename std::span<const Event>::iterator> auto onCoarse, |
| 82 |
float threshold = 1.0) |
| 83 |
{ |
| 84 |
if (events.empty()) return; |
| 85 |
|
| 86 |
std::optional<float> coarseStart; |
| 87 |
decltype(events.begin()) coarseBeginIt; |
| 88 |
auto flushCoarse = [&](float endPixel, auto endIt) { |
| 89 |
if (coarseStart) { |
| 90 |
onCoarse(*coarseStart, endPixel, coarseBeginIt, endIt); |
| 91 |
coarseStart = {}; |
| 92 |
coarseBeginIt = {}; |
| 93 |
} |
| 94 |
}; |
| 95 |
|
| 96 |
auto currentIt = events.begin(); |
| 97 |
auto currentPixel = mapper.timeToX(currentIt->time); |
| 98 |
while (currentIt != events.end()) { |
| 99 |
|
| 100 |
const auto nextIt = std::next(currentIt); |
| 101 |
const auto nextTimestamp = (nextIt != events.end()) |
| 102 |
? nextIt->time |
| 103 |
: endTime; |
| 104 |
const auto nextPixel = mapper.timeToX(nextTimestamp); |
| 105 |
const auto spacing = nextPixel - currentPixel; |
| 106 |
|
| 107 |
if (spacing > threshold) { |
| 108 |
|
| 109 |
flushCoarse(currentPixel, nextIt); |
| 110 |
onDetailed(currentPixel, nextPixel, *currentIt); |
| 111 |
currentIt = nextIt; |
| 112 |
currentPixel = nextPixel; |
| 113 |
} else { |
| 114 |
|
| 115 |
if (!coarseStart) { |
| 116 |
coarseStart = currentPixel; |
| 117 |
coarseBeginIt = currentIt; |
| 118 |
} |
| 119 |
|
| 120 |
|
| 121 |
const auto targetPixel = std::floor(currentPixel) + 1.0f; |
| 122 |
const auto targetTime = mapper.xToTime(targetPixel); |
| 123 |
|
| 124 |
|
| 125 |
|
| 126 |
|
| 127 |
if (auto it = std::ranges::upper_bound(nextIt, events.end(), targetTime, {}, &Event::time); |
| 128 |
it != nextIt) { |
| 129 |
currentIt = std::prev(it); |
| 130 |
currentPixel = mapper.timeToX(currentIt->time); |
| 131 |
} else { |
| 132 |
|
| 133 |
currentIt = nextIt; |
| 134 |
currentPixel = nextPixel; |
| 135 |
} |
| 136 |
} |
| 137 |
} |
| 138 |
|
| 139 |
|
| 140 |
flushCoarse(currentPixel, currentIt); |
| 141 |
} |
| 142 |
|
| 143 |
} |
| 144 |
|
| 145 |
#endif |