Merge remote-tracking branch 'origin/master' into claude/issue-123

# Conflicts:
#	implementations/Crafter.Graphics-UI-Shared.cpp
#	interfaces/Crafter.Graphics-UI.cppm
This commit is contained in:
catbot 2026-06-18 13:29:49 +00:00
commit 88541bf44e
5 changed files with 99 additions and 30 deletions

View file

@ -350,25 +350,63 @@ export namespace Crafter {
// only needs a translate by (x + alignShift, baselineY). The full
// string is part of the key (compared on lookup) so hash collisions
// can't return the wrong run.
//
// The map owns its key string, but lookups happen every frame on the
// onBuild path. To avoid copying the string into a key on each cache
// hit, the hash and equality functors are transparent (is_transparent)
// and accept a borrowing view key whose text is a std::string_view;
// the owning std::string is only materialised on a miss, for emplace.
struct ShapedRunKey {
const Font* font;
float pxSize;
std::array<float, 4> color;
std::string text;
bool operator==(const ShapedRunKey&) const = default;
};
struct ShapedRunViewKey {
const Font* font;
float pxSize;
std::array<float, 4> color;
std::string_view text;
};
struct ShapedRunKeyHash {
std::size_t operator()(const ShapedRunKey& k) const noexcept {
std::size_t h = std::hash<const void*>{}(k.font);
using is_transparent = void;
static std::size_t Hash(const Font* font, float pxSize,
const std::array<float, 4>& color,
std::string_view text) noexcept {
std::size_t h = std::hash<const void*>{}(font);
auto mix = [&h](std::size_t v) noexcept {
h ^= v + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
};
mix(std::hash<std::uint32_t>{}(std::bit_cast<std::uint32_t>(k.pxSize)));
for (float c : k.color)
mix(std::hash<std::uint32_t>{}(std::bit_cast<std::uint32_t>(pxSize)));
for (float c : color)
mix(std::hash<std::uint32_t>{}(std::bit_cast<std::uint32_t>(c)));
mix(std::hash<std::string_view>{}(k.text));
mix(std::hash<std::string_view>{}(text));
return h;
}
std::size_t operator()(const ShapedRunKey& k) const noexcept {
return Hash(k.font, k.pxSize, k.color, k.text);
}
std::size_t operator()(const ShapedRunViewKey& k) const noexcept {
return Hash(k.font, k.pxSize, k.color, k.text);
}
};
struct ShapedRunKeyEqual {
using is_transparent = void;
static bool Eq(const Font* fa, float pa, const std::array<float, 4>& ca,
std::string_view ta,
const Font* fb, float pb, const std::array<float, 4>& cb,
std::string_view tb) noexcept {
return fa == fb && pa == pb && ca == cb && ta == tb;
}
bool operator()(const ShapedRunKey& a, const ShapedRunKey& b) const noexcept {
return Eq(a.font, a.pxSize, a.color, a.text, b.font, b.pxSize, b.color, b.text);
}
bool operator()(const ShapedRunViewKey& a, const ShapedRunKey& b) const noexcept {
return Eq(a.font, a.pxSize, a.color, a.text, b.font, b.pxSize, b.color, b.text);
}
bool operator()(const ShapedRunKey& a, const ShapedRunViewKey& b) const noexcept {
return Eq(a.font, a.pxSize, a.color, a.text, b.font, b.pxSize, b.color, b.text);
}
};
struct ShapedRun {
std::vector<GlyphItem> glyphs; // origin-relative
@ -387,7 +425,7 @@ export namespace Crafter {
// unaffected either way; this just avoids the periodic full-UI reshape
// spike the old clear() caused.
static constexpr std::size_t kMaxShapedRuns = 8192;
std::unordered_map<ShapedRunKey, ShapedRun, ShapedRunKeyHash> shapedRuns_;
std::unordered_map<ShapedRunKey, ShapedRun, ShapedRunKeyHash, ShapedRunKeyEqual> shapedRuns_;
// LRU recency order for shapedRuns_; front = most recently used. Holds
// pointers to the keys owned by the map — stable across rehash because
// unordered_map is node-based — so eviction never re-hashes the world.