Skip to content

Commit df9cb11

Browse files
committed
qml: update block clock at minute resolution
1 parent 58efbf4 commit df9cb11

3 files changed

Lines changed: 72 additions & 16 deletions

File tree

qml/models/blockclockmodel.cpp

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,28 @@
1212

1313
#include <QTime>
1414

15-
BlockClockModel::BlockClockModel(HistoryLoader history_loader, bool start_timer, QObject* parent)
15+
BlockClockModel::BlockClockModel(HistoryLoader history_loader, bool start_timer,
16+
CurrentTimeProvider current_time_provider, QObject* parent)
1617
: QObject{parent},
1718
m_history_loader{std::move(history_loader)},
19+
m_current_time_provider{std::move(current_time_provider)},
1820
m_clock_timer{this}
1921
{
20-
m_clock_timer.setInterval(1000);
22+
if (!m_current_time_provider) {
23+
m_current_time_provider = [] { return QDateTime::currentDateTime(); };
24+
}
25+
26+
m_clock_timer.setSingleShot(true);
27+
m_clock_timer.setTimerType(Qt::PreciseTimer);
2128
connect(&m_clock_timer, &QTimer::timeout, this, [this] {
22-
updateCurrentTime(QDateTime::currentDateTime());
29+
const QDateTime current_time{m_current_time_provider()};
30+
updateCurrentTime(current_time);
31+
scheduleNextClockUpdate(current_time);
2332
});
2433

25-
updateCurrentTime(QDateTime::currentDateTime());
26-
if (start_timer) m_clock_timer.start();
34+
const QDateTime current_time{m_current_time_provider()};
35+
updateCurrentTime(current_time);
36+
if (start_timer) scheduleNextClockUpdate(current_time);
2737
}
2838

2939
qint64 BlockClockModel::PeriodStartFor(const QDateTime& current_time)
@@ -53,6 +63,13 @@ void BlockClockModel::updateCurrentTime(const QDateTime& current_time)
5363
}
5464
}
5565

66+
void BlockClockModel::scheduleNextClockUpdate(const QDateTime& current_time)
67+
{
68+
const QTime time{current_time.time()};
69+
const int milliseconds_into_minute{time.second() * 1000 + time.msec()};
70+
m_clock_timer.start(CLOCK_UPDATE_INTERVAL_MS - milliseconds_into_minute);
71+
}
72+
5673
void BlockClockModel::initializeHistory()
5774
{
5875
m_history_initialized = true;
@@ -61,6 +78,8 @@ void BlockClockModel::initializeHistory()
6178

6279
void BlockClockModel::recordBlockTime(qint64 block_timestamp)
6380
{
81+
updateCurrentTime(m_current_time_provider());
82+
6483
const qint64 period_end{m_timeline.period_start + PERIOD_SECONDS};
6584
if (block_timestamp < m_timeline.period_start || block_timestamp >= period_end) return;
6685

qml/models/blockclockmodel.h

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,14 @@ struct BlockClockTimeline
3232
/**
3333
* Maintains block-clock time and history independently of its presentation.
3434
*
35-
* This model remains current while the dial is hidden. The once-per-second
36-
* currentTimeFraction signal is separate from blockTimeFractions so a clock
37-
* tick never republishes the full block history. Fractions are normalized to
38-
* the current twelve-hour period and are always in the range [0, 1]. An empty
39-
* blockTimeFractions list is valid: it means the active chain contains no
40-
* blocks timestamped in the displayed period, not that synchronization is
41-
* incomplete. Initial-sync state belongs to NodeModel.
35+
* This model remains current while the dial is hidden. currentTimeFraction is
36+
* refreshed at minute boundaries and when a block arrives. It is separate
37+
* from blockTimeFractions so a clock tick never republishes the full block
38+
* history. Fractions are normalized to the current twelve-hour period and are
39+
* always in the range [0, 1]. An empty blockTimeFractions list is valid: it
40+
* means the active chain contains no blocks timestamped in the displayed
41+
* period, not that synchronization is incomplete. Initial-sync state belongs
42+
* to NodeModel.
4243
*
4344
* All methods and the owned timer run on this object's thread (the GUI thread
4445
* in production). History loading is infrequent: once after node
@@ -53,9 +54,12 @@ class BlockClockModel : public QObject
5354

5455
public:
5556
static constexpr qint64 PERIOD_SECONDS{12 * 60 * 60};
57+
static constexpr int CLOCK_UPDATE_INTERVAL_MS{60 * 1000};
5658
using HistoryLoader = std::function<QList<qint64>(qint64 period_start, qint64 period_end)>;
59+
using CurrentTimeProvider = std::function<QDateTime()>;
5760

58-
explicit BlockClockModel(HistoryLoader history_loader = {}, bool start_timer = true, QObject* parent = nullptr);
61+
explicit BlockClockModel(HistoryLoader history_loader = {}, bool start_timer = true,
62+
CurrentTimeProvider current_time_provider = {}, QObject* parent = nullptr);
5963

6064
qint64 periodStart() const { return m_timeline.period_start; }
6165
qreal currentTimeFraction() const { return m_current_time_fraction; }
@@ -81,12 +85,14 @@ public Q_SLOTS:
8185
void blockTimeFractionsChanged();
8286

8387
private:
88+
void scheduleNextClockUpdate(const QDateTime& current_time);
8489
void loadHistory();
8590
void replaceBlockHistory(QList<qint64> block_timestamps);
8691
void rebuildBlockTimeFractions();
8792
qreal fractionForTimestamp(qint64 timestamp) const;
8893

8994
HistoryLoader m_history_loader;
95+
CurrentTimeProvider m_current_time_provider;
9096
QTimer m_clock_timer;
9197
BlockClockTimeline m_timeline;
9298
qreal m_current_time_fraction{0.0};

test/test_blockclockmodel.cpp

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ class BlockClockModelTests : public QObject
2323
private Q_SLOTS:
2424
void currentTimeUsesNamedTwelveHourFraction();
2525
void unchangedSecondDoesNotRepublishCurrentTime();
26+
void blockArrivalRefreshesCurrentTime();
2627
void blockHistoryIsSortedUniqueAndPeriodBounded();
2728
void historyLoadsOnInitializationAndPeriodRollover();
2829
void emptyHistoryDoesNotEmitRedundantChanges();
2930
void timerHasModelOwnershipAndCanBeDisabledForTests();
31+
void timerAlignsToNextMinute();
3032
};
3133

3234
void BlockClockModelTests::currentTimeUsesNamedTwelveHourFraction()
@@ -51,9 +53,26 @@ void BlockClockModelTests::unchangedSecondDoesNotRepublishCurrentTime()
5153
QCOMPARE(current_time_spy.count(), 1);
5254
}
5355

56+
void BlockClockModelTests::blockArrivalRefreshesCurrentTime()
57+
{
58+
QDateTime current_time{UtcTime(15)};
59+
BlockClockModel model{{}, false, [&] { return current_time; }};
60+
QSignalSpy current_time_spy{&model, &BlockClockModel::currentTimeFractionChanged};
61+
62+
current_time = UtcTime(15, 1);
63+
model.recordBlockTime(UtcTime(15, 0, 30).toSecsSinceEpoch());
64+
65+
QCOMPARE(current_time_spy.count(), 1);
66+
QVERIFY(qAbs(model.currentTimeFraction() - (181.0 / 720.0)) < 0.000001);
67+
QCOMPARE(model.blockTimeFractions().size(), 1);
68+
QVERIFY(qAbs(model.blockTimeFractions().constFirst() -
69+
(10830.0 / BlockClockModel::PERIOD_SECONDS)) < 0.000001);
70+
}
71+
5472
void BlockClockModelTests::blockHistoryIsSortedUniqueAndPeriodBounded()
5573
{
56-
BlockClockModel model{{}, false};
74+
const QDateTime current_time{UtcTime(15)};
75+
BlockClockModel model{{}, false, [current_time] { return current_time; }};
5776
model.updateCurrentTime(UtcTime(15));
5877
const qint64 start{model.periodStart()};
5978
QSignalSpy history_spy{&model, &BlockClockModel::blockTimeFractionsChanged};
@@ -106,15 +125,27 @@ void BlockClockModelTests::emptyHistoryDoesNotEmitRedundantChanges()
106125

107126
void BlockClockModelTests::timerHasModelOwnershipAndCanBeDisabledForTests()
108127
{
109-
BlockClockModel stopped_model{{}, false};
128+
const QDateTime current_time{UtcTime(15)};
129+
const auto current_time_provider{[current_time] { return current_time; }};
130+
BlockClockModel stopped_model{{}, false, current_time_provider};
110131
QCOMPARE(stopped_model.timerActive(), false);
111132
QCOMPARE(stopped_model.findChildren<QTimer*>().size(), 1);
112133
QCOMPARE(stopped_model.findChildren<QTimer*>().constFirst()->parent(), &stopped_model);
113134

114-
BlockClockModel running_model{{}, true};
135+
BlockClockModel running_model{{}, true, current_time_provider};
115136
QCOMPARE(running_model.timerActive(), true);
116137
}
117138

139+
void BlockClockModelTests::timerAlignsToNextMinute()
140+
{
141+
const QDateTime current_time{UtcTime(15, 0, 45).addMSecs(250)};
142+
BlockClockModel model{{}, true, [current_time] { return current_time; }};
143+
QTimer* timer{model.findChildren<QTimer*>().constFirst()};
144+
145+
QCOMPARE(timer->timerType(), Qt::PreciseTimer);
146+
QCOMPARE(timer->interval(), BlockClockModel::CLOCK_UPDATE_INTERVAL_MS - 45250);
147+
}
148+
118149
#ifdef BITCOINQML_NO_TEST_MAIN
119150
BITCOINQML_REGISTER_QT_TEST(BlockClockModelTests)
120151
#else

0 commit comments

Comments
 (0)