6 #if __has_include(<fmt/format.h>)
12 # include <fmt/format.h>
13 # include <functional>
16 # include <string_view>
23 inline auto color(std::string_view recordCoordTags) -> std::uint32_t
25 auto c =
static_cast<uint32_t
>(std::hash<std::string_view>{}(recordCoordTags) &std::size_t{0xFFFFFF});
31 inline auto xmlEscape(
const std::string& str) -> std::string
34 result.reserve(str.size());
35 for(
const char c : str)
40 result.append(
"&");
43 result.append(
""");
46 result.append(
"'");
49 result.append(
"<");
52 result.append(
">");
62 template<
typename T, std::
size_t Dim>
63 auto formatArrayIndex(
const ArrayIndex<T, Dim>& ai)
65 if constexpr(Dim == 1)
66 return std::to_string(ai[0]);
74 s += std::to_string(v);
81 template<
typename ArrayIndex>
87 std::string_view recordCoordTags;
88 NrAndOffset<std::size_t> nrAndOffset;
92 template<
typename View>
93 void fillBlobsWithPattern(View& view, uint8_t pattern)
95 const auto& mapping = view.mapping();
96 for(std::size_t i = 0; i < View::Mapping::blobCount; i++)
97 std::memset(&view.blobs()[i][0], pattern, mapping.blobSize(i));
100 template<
typename View,
typename RecordCoord>
101 void boxesFromComputedField(
103 typename View::Mapping::ArrayExtents::Index ai,
105 std::vector<FieldBox<typename View::Mapping::ArrayExtents::Index>>& infos)
108 using RecordDim =
typename Mapping::RecordDim;
110 auto emitInfo = [&](
auto nrAndOffset, std::size_t size)
114 flatRecordCoord<RecordDim, RecordCoord>,
115 prettyRecordCoord<RecordDim>(rc),
120 using Type =
GetType<RecordDim, decltype(rc)>;
122 auto& blobs = view.blobs();
123 auto&& ref = view.mapping().compute(ai, rc, blobs);
126 if constexpr(std::is_lvalue_reference_v<decltype(ref)>)
128 auto address =
reinterpret_cast<std::intptr_t
>(&ref);
129 for(std::size_t i = 0; i < blobs.size(); i++)
133 const auto front =
reinterpret_cast<std::intptr_t
>(&blobs[i][0]);
134 const auto back =
reinterpret_cast<std::intptr_t
>(&blobs[i][view.mapping().blobSize(i) - 1]);
135 if(front <= address && address <= back)
137 emitInfo(
NrAndOffset{i,
static_cast<std::size_t
>(address - front)},
sizeof(Type));
143 if constexpr(std::is_default_constructible_v<Type>)
145 const auto infosBefore = infos.size();
148 const auto pattern = std::uint8_t{0xFF};
149 fillBlobsWithPattern(view, pattern);
152 auto wasTouched = [&](
auto b) {
return static_cast<std::uint8_t
>(b) != pattern; };
153 for(std::size_t i = 0; i < Mapping::blobCount; i++)
155 const auto blobSize = view.mapping().blobSize(i);
156 const auto* begin = &blobs[i][0];
157 const auto* end = begin + blobSize;
159 auto* searchBegin = begin;
162 const auto* touchedBegin = std::find_if(searchBegin, end, wasTouched);
163 if(touchedBegin == end)
165 const auto& touchedEnd = std::find_if_not(touchedBegin + 1, end, wasTouched);
167 NrAndOffset{i,
static_cast<std::size_t
>(touchedBegin - begin)},
168 touchedEnd - touchedBegin);
169 if(touchedEnd == end)
171 searchBegin = touchedEnd + 1;
175 if(infosBefore != infos.size())
180 emitInfo(
NrAndOffset{Mapping::blobCount, std::size_t{0}},
sizeof(Type));
183 template<
typename Mapping>
184 auto boxesFromMapping(
const Mapping& mapping) -> std::vector<FieldBox<typename Mapping::ArrayExtents::Index>>
186 std::vector<FieldBox<typename Mapping::ArrayExtents::Index>> infos;
188 std::optional<decltype(
allocView(mapping))> view;
189 if constexpr(hasAnyComputedField<Mapping>)
192 using RecordDim =
typename Mapping::RecordDim;
193 for(
auto ai : ArrayIndexRange{mapping.extents()})
194 forEachLeafCoord<RecordDim>(
197 using Type =
GetType<RecordDim, decltype(rc)>;
199 boxesFromComputedField(view.value(), ai, rc, infos);
202 const auto [nr, off] = mapping.blobNrAndOffset(ai, rc);
206 prettyRecordCoord<RecordDim>(rc),
207 {
static_cast<std::size_t
>(nr),
static_cast<std::size_t
>(off)},
215 template<
typename ArrayIndex>
216 auto breakBoxes(std::vector<FieldBox<ArrayIndex>> boxes, std::size_t wrapByteCount)
217 -> std::vector<FieldBox<ArrayIndex>>
219 for(std::size_t i = 0; i < boxes.size(); i++)
222 if(fb.nrAndOffset.offset / wrapByteCount != (fb.nrAndOffset.offset + fb.size - 1) / wrapByteCount)
224 const auto remainingSpace = wrapByteCount - fb.nrAndOffset.offset % wrapByteCount;
226 newFb.nrAndOffset.offset = fb.nrAndOffset.offset + remainingSpace;
227 newFb.size = fb.size - remainingSpace;
228 fb.size = remainingSpace;
229 boxes.push_back(newFb);
235 inline auto cssClass(std::string tags)
237 std::replace(begin(tags), end(tags),
'.',
'_');
238 std::replace(begin(tags), end(tags),
'<',
'_');
239 std::replace(begin(tags), end(tags),
'>',
'_');
249 template<
typename Mapping>
251 const Mapping& mapping,
252 std::size_t wrapByteCount = 64,
253 bool breakBoxes =
true,
254 const std::vector<std::uint32_t>& palette = {},
255 std::string_view textColor =
"black") -> std::string
257 constexpr
auto byteSizeInPixel = 30;
258 constexpr
auto blobBlockWidth = 60;
260 auto infos = internal::boxesFromMapping(mapping);
262 infos = internal::breakBoxes(std::move(infos), wrapByteCount);
266 [](
const auto& a,
const auto& b) {
267 return std::tie(a.nrAndOffset.nr, a.nrAndOffset.offset)
268 < std::tie(b.nrAndOffset.nr, b.nrAndOffset.offset);
273 std::array<int, Mapping::blobCount + hasAnyComputedField<Mapping> + 1> blobYOffset{};
274 auto writeBlobHeader = [&](std::size_t i, std::size_t size, std::string_view name)
276 const auto blobRows = (size + wrapByteCount - 1) / wrapByteCount;
277 blobYOffset[i + 1] = blobYOffset[i] + (blobRows + 1) * byteSizeInPixel;
278 const auto height = blobRows * byteSizeInPixel;
280 R
"a(<rect x="0" y="{}" width="{}" height="{}" fill="#AAA" stroke="#000"/>
281 <text x="{}" y="{}" fill="{}" text-anchor="middle">{}</text>
287 blobYOffset[i] + height / 2,
291 for(std::size_t i = 0; i < Mapping::blobCount; i++)
292 writeBlobHeader(i, mapping.blobSize(i),
"Blob: " + std::to_string(i));
295 R
"(<?xml version="1.0" encoding="UTF-8" standalone="no"?>
296 <svg width="{}" height="{}" xmlns="http://www.w3.org/2000/svg">
298 .label {{ font: {}px sans-serif; }}
301 blobBlockWidth + wrapByteCount * byteSizeInPixel,
302 blobYOffset.back() == 0 ? 987654321 : blobYOffset.back() - byteSizeInPixel,
306 std::size_t computedSizeSoFar = 0;
307 std::size_t lastBlobNr = std::numeric_limits<std::size_t>::max();
308 std::size_t usedBytesInBlobSoFar = 0;
309 for(
const auto& info : infos)
311 if(lastBlobNr != info.nrAndOffset.nr)
313 usedBytesInBlobSoFar = 0;
314 lastBlobNr = info.nrAndOffset.nr;
317 const auto blobY = blobYOffset[info.nrAndOffset.nr];
318 const auto offset = [&]
320 if(info.nrAndOffset.nr < Mapping::blobCount)
321 return info.nrAndOffset.offset;
323 const auto offset = computedSizeSoFar;
324 computedSizeSoFar += info.size;
327 auto x = (offset % wrapByteCount) * byteSizeInPixel + blobBlockWidth;
328 auto y = (offset / wrapByteCount) * byteSizeInPixel + blobY;
329 const auto fillColor = [&]
332 return internal::color(info.recordCoordTags);
333 return palette[info.flatRecordCoord % palette.size()];
335 const auto width = byteSizeInPixel * info.size;
337 const auto nextOffset = [&]
339 if(&info == &infos.back())
340 return std::numeric_limits<std::size_t>::max();
341 const auto& nextInfo = (&info)[1];
342 if(info.nrAndOffset.nr < Mapping::blobCount && info.nrAndOffset.nr == nextInfo.nrAndOffset.nr)
343 return nextInfo.nrAndOffset.offset;
345 return std::numeric_limits<std::size_t>::max();
347 const auto isOverlapped = offset < usedBytesInBlobSoFar || nextOffset < offset + info.size;
348 usedBytesInBlobSoFar = offset + info.size;
350 constexpr
auto cropBoxes =
true;
354 R
"(<svg x="{}" y="{}" width="{}" height="{}">
364 R"(<rect x="{}" y="{}" width="{}" height="{}" fill="#{:06X}" stroke="#000" fill-opacity="{}"/>
371 isOverlapped ? 0.3 : 1.0);
372 for(std::size_t i = 1; i < info.size; i++)
375 R
"(<line x1="{}" y1="{}" x2="{}" y2="{}" stroke="#777"/>
377 x + i * byteSizeInPixel,
378 y + byteSizeInPixel * 2 / 3,
379 x + i * byteSizeInPixel,
380 y + byteSizeInPixel);
383 R"(<text x="{}" y="{}" fill="{}" text-anchor="middle" class="label">{} {}</text>
386 y + byteSizeInPixel * 3 / 4,
388 internal::formatArrayIndex(info.arrayIndex),
389 internal::xmlEscape(std::string{info.recordCoordTags}));
395 if(hasAnyComputedField<Mapping>)
397 if(computedSizeSoFar > 0)
398 writeBlobHeader(Mapping::blobCount, computedSizeSoFar,
"Comp.");
401 const auto blobRows = (wrapByteCount - 1) / wrapByteCount;
402 blobYOffset[Mapping::blobCount + 1]
403 = blobYOffset[Mapping::blobCount] + blobRows * byteSizeInPixel;
407 const auto i = svg.find(
"987654321");
408 assert(i != std::string::npos);
409 svg.replace(i, 9, std::to_string(blobYOffset.back() - byteSizeInPixel));
417 template<
typename Mapping>
418 auto toSvg(
const Mapping& mapping,
const std::vector<std::uint32_t>& palette, std::string_view textColor =
"#000")
421 return toSvg(mapping, 64,
true, palette, textColor);
427 template<
typename Mapping>
428 auto toHtml(
const Mapping& mapping) -> std::string
430 constexpr
auto byteSizeInPixel = 30;
431 constexpr
auto rulerLengthInBytes = 512;
432 constexpr
auto rulerByteInterval = 8;
434 auto infos = internal::boxesFromMapping(mapping);
438 [](
const auto& a,
const auto& b) {
439 return std::tie(a.nrAndOffset.nr, a.nrAndOffset.offset)
440 < std::tie(b.nrAndOffset.nr, b.nrAndOffset.offset);
446 [](
const auto& a,
const auto& b) {
return a.nrAndOffset == b.nrAndOffset; }),
457 display: inline-block;
460 background: repeating-linear-gradient(90deg, #0000, #0000 29px, #777 29px, #777 30px);
463 vertical-align: middle;
466 background: repeating-linear-gradient(90deg, #0000, #0000 29px, #000 29px, #000 30px);
467 border-bottom: 1px solid;
473 display: inline-block;
477 using RecordDim =
typename Mapping::RecordDim;
478 forEachLeafCoord<RecordDim>(
481 constexpr
int size =
sizeof(
GetType<RecordDim, decltype(rc)>);
486 background-color: #{:X};
489 internal::cssClass(std::string{prettyRecordCoord<RecordDim>(rc)}),
490 byteSizeInPixel * size,
491 internal::color(prettyRecordCoord<RecordDim>(rc)));
494 html += fmt::format(R"(</style>
499 for(
auto i = 0; i < rulerLengthInBytes; i += rulerByteInterval)
502 <div style="margin-left: {}px;">{}</div>)",
505 html += fmt::format(R"(
509 auto currentBlobNr = std::numeric_limits<std::size_t>::max();
510 for(
const auto& info : infos)
512 if(currentBlobNr != info.nrAndOffset.nr)
514 currentBlobNr = info.nrAndOffset.nr;
515 html += fmt::format(
"<h1>Blob: {}</h1>", currentBlobNr);
518 R
"(<div class="box {0}" title="{1} {2}">{1} {2}</div>)",
519 internal::cssClass(std::string{info.recordCoordTags}),
520 internal::formatArrayIndex(info.arrayIndex),
521 internal::xmlEscape(std::string{info.recordCoordTags}));
ArrayIndex(Args...) -> ArrayIndex< typename internal::IndexTypeFromArgs< std::size_t, Args... >::type, sizeof...(Args)>
NrAndOffset(Int, Int) -> NrAndOffset< Int >
constexpr std::size_t flatRecordCoord
auto allocView(Mapping mapping={}, const Allocator &alloc={}, Accessor accessor={}) -> View< Mapping, internal::AllocatorBlobType< Allocator, typename Mapping::RecordDim >, Accessor >
constexpr bool isComputed
Returns true if the field accessed via the given mapping and record coordinate is a computed value.
typename internal::GetTypeImpl< RecordDim, RecordCoordOrTags... >::type GetType