Skip to content

Commit f076918

Browse files
committed
refactor(F-015): extract debounce_elapsed() to Debounce.hpp + add Catch2 unit tests
- Add src/libslic3r/Debounce.hpp: pure C++ rate-limiter, no wx deps - GUI_App.cpp: use debounce_elapsed() instead of inline chrono block - tests/libslic3r/test_debounce.cpp: 5 Catch2 scenarios covering first-call, suppression, expiry, zero-interval, no-mutation-on-suppress - tests/libslic3r/CMakeLists.txt: register test_debounce.cpp Standalone build verified: g++ -std=c++17 -I src -I tests -DCATCH_CONFIG_MAIN test_debounce.cpp All tests passed (7 assertions in 5 test cases) Note: pre-existing upstream test failures (test_3mf.cpp, test_voronoi.cpp) prevent full libslic3r_tests suite from linking; not introduced by this PR.
1 parent 443aa53 commit f076918

4 files changed

Lines changed: 125 additions & 4 deletions

File tree

src/libslic3r/Debounce.hpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
///|/ Copyright (c) Prusa Research 2023 Vojtěch Bubník @bubnikv
2+
///|/
3+
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
4+
///|/
5+
#ifndef slic3r_Debounce_hpp_
6+
#define slic3r_Debounce_hpp_
7+
8+
#include <chrono>
9+
10+
namespace Slic3r {
11+
12+
// Rate-limit a recurring action.
13+
//
14+
// Returns true and updates `last_tp` when at least `interval` has elapsed
15+
// since the last accepted call. On the very first call (last_tp ==
16+
// time_point{}) the action is always accepted.
17+
//
18+
// Usage:
19+
// static auto s_last = std::chrono::steady_clock::time_point{};
20+
// if (debounce_elapsed(s_last, std::chrono::seconds(5)))
21+
// do_expensive_thing();
22+
//
23+
// The function is intentionally free of wx / GUI dependencies so it can be
24+
// exercised by the plain libslic3r unit-test suite.
25+
inline bool debounce_elapsed(
26+
std::chrono::steady_clock::time_point &last_tp,
27+
std::chrono::steady_clock::duration interval)
28+
{
29+
const auto now = std::chrono::steady_clock::now();
30+
if (now - last_tp >= interval) {
31+
last_tp = now;
32+
return true;
33+
}
34+
return false;
35+
}
36+
37+
} // namespace Slic3r
38+
39+
#endif // slic3r_Debounce_hpp_

src/slic3r/GUI/GUI_App.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
#include <wx/glcanvas.h>
5757

5858
#include "libslic3r/Utils.hpp"
59+
#include "libslic3r/Debounce.hpp"
5960
#include "libslic3r/Model.hpp"
6061
#include "libslic3r/I18N.hpp"
6162
#include "libslic3r/LogSink.hpp"
@@ -3323,11 +3324,8 @@ bool GUI_App::on_init_inner()
33233324
// Debounce: avoid synchronous disk writes on every idle event.
33243325
// Save at most once per 5 seconds; OnExit() flushes any remaining dirty state.
33253326
static auto s_last_config_save = std::chrono::steady_clock::time_point{};
3326-
const auto now = std::chrono::steady_clock::now();
3327-
if (now - s_last_config_save >= std::chrono::seconds(5)) {
3327+
if (Slic3r::debounce_elapsed(s_last_config_save, std::chrono::seconds(5)))
33283328
app_config->save();
3329-
s_last_config_save = now;
3330-
}
33313329
}
33323330

33333331
// BBS

tests/libslic3r/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ add_executable(${_TEST_NAME}_tests
2222
test_png_io.cpp
2323
test_timeutils.cpp
2424
test_indexed_triangle_set.cpp
25+
test_debounce.cpp
2526
../libnest2d/printer_parts.cpp
2627
)
2728

tests/libslic3r/test_debounce.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#include <catch2/catch.hpp>
2+
3+
#include "libslic3r/Debounce.hpp"
4+
5+
#include <thread>
6+
7+
using namespace Slic3r;
8+
using namespace std::chrono;
9+
10+
// ---------------------------------------------------------------------------
11+
// debounce_elapsed() — unit tests
12+
// ---------------------------------------------------------------------------
13+
14+
SCENARIO("debounce_elapsed: first call always fires", "[Debounce]") {
15+
GIVEN("A default-initialised time_point (epoch)") {
16+
steady_clock::time_point last{};
17+
WHEN("debounce_elapsed is called with a 5-second interval") {
18+
const bool fired = debounce_elapsed(last, seconds(5));
19+
THEN("It returns true") {
20+
REQUIRE(fired);
21+
}
22+
THEN("last_tp is updated to a non-epoch value") {
23+
REQUIRE(last != steady_clock::time_point{});
24+
}
25+
}
26+
}
27+
}
28+
29+
SCENARIO("debounce_elapsed: second immediate call is suppressed", "[Debounce]") {
30+
GIVEN("A time_point primed by a first accepted call") {
31+
steady_clock::time_point last{};
32+
debounce_elapsed(last, seconds(5)); // prime
33+
WHEN("debounce_elapsed is called again immediately") {
34+
const bool fired = debounce_elapsed(last, seconds(5));
35+
THEN("It returns false") {
36+
REQUIRE_FALSE(fired);
37+
}
38+
}
39+
}
40+
}
41+
42+
SCENARIO("debounce_elapsed: call fires again after interval expires", "[Debounce]") {
43+
GIVEN("A time_point set to 10 seconds in the past") {
44+
// Simulate 'last' being 10 seconds old without sleeping.
45+
steady_clock::time_point last = steady_clock::now() - seconds(10);
46+
WHEN("debounce_elapsed is called with a 5-second interval") {
47+
const bool fired = debounce_elapsed(last, seconds(5));
48+
THEN("It returns true") {
49+
REQUIRE(fired);
50+
}
51+
THEN("last_tp is updated") {
52+
REQUIRE(last >= steady_clock::now() - milliseconds(100));
53+
}
54+
}
55+
}
56+
}
57+
58+
SCENARIO("debounce_elapsed: zero interval fires every time", "[Debounce]") {
59+
GIVEN("A time_point primed by a first call") {
60+
steady_clock::time_point last{};
61+
debounce_elapsed(last, seconds(0));
62+
WHEN("debounce_elapsed is called immediately again with zero interval") {
63+
const bool fired = debounce_elapsed(last, seconds(0));
64+
THEN("It returns true") {
65+
REQUIRE(fired);
66+
}
67+
}
68+
}
69+
}
70+
71+
SCENARIO("debounce_elapsed: last_tp is not modified on suppressed calls", "[Debounce]") {
72+
GIVEN("A time_point primed by a first accepted call") {
73+
steady_clock::time_point last{};
74+
debounce_elapsed(last, seconds(5));
75+
const auto snapshot = last;
76+
WHEN("A suppressed call is made") {
77+
debounce_elapsed(last, seconds(5));
78+
THEN("last_tp is unchanged") {
79+
REQUIRE(last == snapshot);
80+
}
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)