From bcd98ea7914ed3b3615731701123f5bb674ad1b0 Mon Sep 17 00:00:00 2001 From: Andrew <159703254+andrewsoonqn@users.noreply.github.com> Date: Thu, 9 Jul 2026 16:18:11 +0800 Subject: [PATCH] Add stable hashed identity for speed dial actions --- src/slic3r/GUI/SpeedDialActionId.hpp | 36 +++++++++++++++++++ tests/slic3rutils/CMakeLists.txt | 1 + .../slic3rutils/test_speed_dial_action_id.cpp | 28 +++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 src/slic3r/GUI/SpeedDialActionId.hpp create mode 100644 tests/slic3rutils/test_speed_dial_action_id.cpp diff --git a/src/slic3r/GUI/SpeedDialActionId.hpp b/src/slic3r/GUI/SpeedDialActionId.hpp new file mode 100644 index 0000000000..e900a76182 --- /dev/null +++ b/src/slic3r/GUI/SpeedDialActionId.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +namespace Slic3r { namespace GUI { + +// Stable, opaque identity for a speed-dial action, used as the AppConfig key, +// the JS/registry handle, and the favourite_actions/stats key. FNV-1a 64-bit over +// the UTF-8 bytes of `plugin_key 0x1f capability`, lowercase 16-char hex. +// +// why: this is PERSISTED, so it must be deterministic across runs and platforms. +// std::hash is explicitly NOT usable here (unspecified, per-process). +// note: 64-bit hash -> collision is astronomically unlikely at the tens-of-actions +// scale; upgrade path (128-bit / full-string key) only if that ever changes. +inline std::string speed_dial_action_id(const std::string& plugin_key, const std::string& capability) +{ + constexpr std::uint64_t kOffset = 14695981039346656037ULL; // FNV offset basis (0xcbf29ce484222325) + constexpr std::uint64_t kPrime = 1099511628211ULL; // FNV prime + std::uint64_t h = kOffset; + // note: [&], not [&h] - MSVC rejects the constexpr kPrime read inside the lambda + // without a default capture mode (error C3493). + auto mix = [&](const std::string& s) { + for (unsigned char c : s) { h ^= c; h *= kPrime; } + }; + mix(plugin_key); + h ^= 0x1f; h *= kPrime; // 0x1f (unit separator) between the two fields + mix(capability); + + static const char* kHex = "0123456789abcdef"; + std::string out(16, '0'); + for (int i = 15; i >= 0; --i) { out[i] = kHex[h & 0xf]; h >>= 4; } + return out; +} + +}} // namespace Slic3r::GUI diff --git a/tests/slic3rutils/CMakeLists.txt b/tests/slic3rutils/CMakeLists.txt index 1e62c1a6ba..490f56a097 100644 --- a/tests/slic3rutils/CMakeLists.txt +++ b/tests/slic3rutils/CMakeLists.txt @@ -5,6 +5,7 @@ add_executable(${_TEST_NAME}_tests test_plugin_capability_identifier.cpp test_plugin_install.cpp test_plugin_sort.cpp + test_speed_dial_action_id.cpp ) if (MSVC) diff --git a/tests/slic3rutils/test_speed_dial_action_id.cpp b/tests/slic3rutils/test_speed_dial_action_id.cpp new file mode 100644 index 0000000000..c2667df839 --- /dev/null +++ b/tests/slic3rutils/test_speed_dial_action_id.cpp @@ -0,0 +1,28 @@ +#include + +#include + +using Slic3r::GUI::speed_dial_action_id; + +TEST_CASE("speed_dial_action_id is deterministic and opaque", "[speeddial][id]") { + const std::string a = speed_dial_action_id("pack.py", "Do Thing"); + const std::string b = speed_dial_action_id("pack.py", "Do Thing"); + CHECK(a == b); // stable within a run + CHECK(a.size() == 16); // 64-bit -> 16 hex chars + CHECK(a.find_first_not_of("0123456789abcdef") == std::string::npos); +} + +TEST_CASE("speed_dial_action_id separates key and capability", "[speeddial][id]") { + // The 0x1f separator prevents (key+cap) run-together collisions: + // ("ab","c") must not equal ("a","bc"). + CHECK(speed_dial_action_id("ab", "c") != speed_dial_action_id("a", "bc")); + CHECK(speed_dial_action_id("k", "one") != speed_dial_action_id("k", "two")); + CHECK(speed_dial_action_id("k1", "cap") != speed_dial_action_id("k2", "cap")); +} + +TEST_CASE("speed_dial_action_id golden vector locks cross-run stability", "[speeddial][id]") { + // why: this value is PERSISTED as a config key; if the algorithm ever drifts, + // every saved favourite/stat silently orphans. Pin a known input->output. + // FNV-1a 64-bit of bytes: 'k','e','y',0x1f,'c','a','p' + CHECK(speed_dial_action_id("key", "cap") == "c54ad154fb2c9187"); +}