GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 80.6% 29 / 0 / 36
Functions: 100.0% 6 / 0 / 6
Branches: 56.7% 17 / 0 / 30

src/input_key_cache.hpp
Line Branch Exec Source
1 #ifndef DETOURMODKIT_INPUT_KEY_CACHE_HPP
2 #define DETOURMODKIT_INPUT_KEY_CACHE_HPP
3
4 /**
5 * @file input_key_cache.hpp
6 * @brief Internal per-poll-cycle memoization of virtual-key down-state.
7 * @details Poll-thread-private helper used by InputPoller's binding-evaluation pass. Windows-agnostic and
8 * allocation-free so it can be unit-tested off a live message queue (the down-state probe is injected),
9 * mirroring the src-internal header pattern used by input_intercept.hpp. Not installed.
10 */
11
12 #include <array>
13 #include <cstddef>
14 #include <cstdint>
15
16 namespace DetourModKit::detail
17 {
18 /**
19 * @class KeyStateCache
20 * @brief Memoizes a virtual key's down-state for the span of one poll cycle.
21 * @details A single cycle reads the same modifier/trigger VK many times: once per binding that references it,
22 * plus the strict known-modifier rescan that rejects bindings holding an unrelated modifier. Probing
23 * each distinct VK at most once per cycle collapses that fan-out to one read per VK and hands every read
24 * this cycle a single coherent snapshot, matching the cycle-scoped gamepad and wheel snapshots.
25 *
26 * GetAsyncKeyState reads the global asynchronous key state, not a thread-specific message queue, so it is
27 * the correct primitive for a poll thread that does not pump messages. Memoization deliberately makes the
28 * poll loop cycle-coherent: a transition that happens after a VK's first probe is observed on the next
29 * cycle rather than splitting two bindings in the same cycle. The probe is injected as a callable so the
30 * poll loop binds it to GetAsyncKeyState while tests drive it deterministically and count invocations.
31 */
32 class KeyStateCache
33 {
34 public:
35 /// Clears every sample; call once at the start of each poll cycle so the next cycle re-probes.
36 102 void reset() noexcept { m_state.fill(UNKNOWN); }
37
38 /**
39 * @brief Returns whether @p vk is down, invoking @p probe at most once per cycle for each distinct VK.
40 * @tparam Probe Callable `bool(int vk)` returning the live down-state; must not throw.
41 * @param vk Windows virtual-key code (keyboard or mouse button). Codes outside 0..255 read as not pressed
42 * and are never probed.
43 * @param probe Down-state probe, called only on the first read of @p vk this cycle.
44 * @return The cycle's cached down-state for @p vk.
45 */
46 1095 template <typename Probe> [[nodiscard]] bool pressed(int vk, Probe &&probe) noexcept
47 {
48 1095 const auto index = static_cast<unsigned>(vk);
49
5/10
bool DetourModKit::detail::KeyStateCache::pressed<KeyStateCacheTest_ResetReArmsForNextCycle_Test::TestBody()::{lambda(int)#1}&>(int, KeyStateCacheTest_ResetReArmsForNextCycle_Test::TestBody()::{lambda(int)#1}&):
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 2 times.
bool DetourModKit::detail::KeyStateCache::pressed<KeyStateCacheTest_CachesUpStateWithoutReProbing_Test::TestBody()::{lambda(int)#1}&>(int, KeyStateCacheTest_CachesUpStateWithoutReProbing_Test::TestBody()::{lambda(int)#1}&):
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 2 times.
bool DetourModKit::detail::KeyStateCache::pressed<KeyStateCacheTest_ProbesEachDistinctVkOncePerCycle_Test::TestBody()::{lambda(int)#1}&>(int, KeyStateCacheTest_ProbesEachDistinctVkOncePerCycle_Test::TestBody()::{lambda(int)#1}&):
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 5 times.
bool DetourModKit::detail::KeyStateCache::pressed<KeyStateCacheTest_OutOfRangeVkReadsAsNotPressedWithoutProbing_Test::TestBody()::{lambda(int)#1}&>(int, KeyStateCacheTest_OutOfRangeVkReadsAsNotPressedWithoutProbing_Test::TestBody()::{lambda(int)#1}&):
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 6 not taken.
bool DetourModKit::detail::KeyStateCache::pressed<DetourModKit::(anonymous namespace)::is_code_pressed(DetourModKit::InputCode const&, DetourModKit::detail::KeyStateCache&, _XINPUT_STATE const&, bool, int, int, unsigned char)::{lambda(int)#1}>(int, DetourModKit::(anonymous namespace)::is_code_pressed(DetourModKit::InputCode const&, DetourModKit::detail::KeyStateCache&, _XINPUT_STATE const&, bool, int, int, unsigned char)::{lambda(int)#1}&&):
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1083 times.
2190 if (index >= m_state.size())
50 {
51 3 return false;
52 }
53
7/10
bool DetourModKit::detail::KeyStateCache::pressed<KeyStateCacheTest_ResetReArmsForNextCycle_Test::TestBody()::{lambda(int)#1}&>(int, KeyStateCacheTest_ResetReArmsForNextCycle_Test::TestBody()::{lambda(int)#1}&):
✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 14 not taken.
bool DetourModKit::detail::KeyStateCache::pressed<KeyStateCacheTest_CachesUpStateWithoutReProbing_Test::TestBody()::{lambda(int)#1}&>(int, KeyStateCacheTest_CachesUpStateWithoutReProbing_Test::TestBody()::{lambda(int)#1}&):
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 14 taken 1 time.
bool DetourModKit::detail::KeyStateCache::pressed<KeyStateCacheTest_ProbesEachDistinctVkOncePerCycle_Test::TestBody()::{lambda(int)#1}&>(int, KeyStateCacheTest_ProbesEachDistinctVkOncePerCycle_Test::TestBody()::{lambda(int)#1}&):
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 14 taken 3 times.
bool DetourModKit::detail::KeyStateCache::pressed<KeyStateCacheTest_OutOfRangeVkReadsAsNotPressedWithoutProbing_Test::TestBody()::{lambda(int)#1}&>(int, KeyStateCacheTest_OutOfRangeVkReadsAsNotPressedWithoutProbing_Test::TestBody()::{lambda(int)#1}&):
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 14 not taken.
bool DetourModKit::detail::KeyStateCache::pressed<DetourModKit::(anonymous namespace)::is_code_pressed(DetourModKit::InputCode const&, DetourModKit::detail::KeyStateCache&, _XINPUT_STATE const&, bool, int, int, unsigned char)::{lambda(int)#1}>(int, DetourModKit::(anonymous namespace)::is_code_pressed(DetourModKit::InputCode const&, DetourModKit::detail::KeyStateCache&, _XINPUT_STATE const&, bool, int, int, unsigned char)::{lambda(int)#1}&&):
✓ Branch 7 → 8 taken 120 times.
✓ Branch 7 → 14 taken 963 times.
1092 if (m_state[index] == UNKNOWN)
54 {
55
5/10
bool DetourModKit::detail::KeyStateCache::pressed<KeyStateCacheTest_ResetReArmsForNextCycle_Test::TestBody()::{lambda(int)#1}&>(int, KeyStateCacheTest_ResetReArmsForNextCycle_Test::TestBody()::{lambda(int)#1}&):
✓ Branch 9 → 10 taken 2 times.
✗ Branch 9 → 11 not taken.
bool DetourModKit::detail::KeyStateCache::pressed<KeyStateCacheTest_CachesUpStateWithoutReProbing_Test::TestBody()::{lambda(int)#1}&>(int, KeyStateCacheTest_CachesUpStateWithoutReProbing_Test::TestBody()::{lambda(int)#1}&):
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 1 time.
bool DetourModKit::detail::KeyStateCache::pressed<KeyStateCacheTest_ProbesEachDistinctVkOncePerCycle_Test::TestBody()::{lambda(int)#1}&>(int, KeyStateCacheTest_ProbesEachDistinctVkOncePerCycle_Test::TestBody()::{lambda(int)#1}&):
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 1 time.
bool DetourModKit::detail::KeyStateCache::pressed<KeyStateCacheTest_OutOfRangeVkReadsAsNotPressedWithoutProbing_Test::TestBody()::{lambda(int)#1}&>(int, KeyStateCacheTest_OutOfRangeVkReadsAsNotPressedWithoutProbing_Test::TestBody()::{lambda(int)#1}&):
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 11 not taken.
bool DetourModKit::detail::KeyStateCache::pressed<DetourModKit::(anonymous namespace)::is_code_pressed(DetourModKit::InputCode const&, DetourModKit::detail::KeyStateCache&, _XINPUT_STATE const&, bool, int, int, unsigned char)::{lambda(int)#1}>(int, DetourModKit::(anonymous namespace)::is_code_pressed(DetourModKit::InputCode const&, DetourModKit::detail::KeyStateCache&, _XINPUT_STATE const&, bool, int, int, unsigned char)::{lambda(int)#1}&&):
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 120 times.
125 m_state[index] = probe(vk) ? PRESSED : RELEASED;
56 }
57 1092 return m_state[index] == PRESSED;
58 }
59
60 private:
61 static constexpr std::size_t VK_COUNT = 256;
62 static constexpr uint8_t UNKNOWN = 0;
63 static constexpr uint8_t PRESSED = 1;
64 static constexpr uint8_t RELEASED = 2;
65
66 // Tri-state per VK indexed by code (0..255): unknown, probed down, or probed up. The "probed up" state is
67 // distinct from "unknown" so a released key is not re-probed within the cycle.
68 std::array<uint8_t, VK_COUNT> m_state{};
69 };
70 } // namespace DetourModKit::detail
71
72 #endif // DETOURMODKIT_INPUT_KEY_CACHE_HPP
73