Merge branch 'millis64-inline-single-thread' into integration

This commit is contained in:
J. Nick Koston
2026-04-12 17:36:15 -10:00
3 changed files with 45 additions and 31 deletions
+1 -1
View File
@@ -290,7 +290,7 @@ class Scheduler {
// because scheduling only compares millis_64 against millis_64 — never against millis().
// On other platforms (ESP8266), extends now to 64-bit using rollover tracking, so both
// millis() and scheduling use the same clock.
uint64_t millis_64_from_(uint32_t now) {
uint64_t ESPHOME_ALWAYS_INLINE millis_64_from_(uint32_t now) {
#ifdef USE_NATIVE_64BIT_TIME
(void) now;
return millis_64();
+12 -30
View File
@@ -20,6 +20,12 @@ namespace esphome {
static const char *const TAG = "time_64";
#endif
#ifdef ESPHOME_THREAD_SINGLE
// Storage for Millis64Impl inline compute() — defined here so all TUs share one copy.
uint32_t Millis64Impl::last_millis_{0};
uint16_t Millis64Impl::millis_major_{0};
#else
uint64_t Millis64Impl::compute(uint32_t now) {
// Half the 32-bit range - used to detect rollovers vs normal time progression
static constexpr uint32_t HALF_MAX_UINT32 = std::numeric_limits<uint32_t>::max() / 2;
@@ -44,51 +50,25 @@ uint64_t Millis64Impl::compute(uint32_t now) {
* to last_millis is provided by its release store and the corresponding acquire loads.
*/
static std::atomic<uint16_t> millis_major{0};
#elif !defined(ESPHOME_THREAD_SINGLE) /* ESPHOME_THREAD_MULTI_NO_ATOMICS */
#else /* ESPHOME_THREAD_MULTI_NO_ATOMICS */
static Mutex lock;
static uint32_t last_millis{0};
static uint16_t millis_major{0};
#else /* ESPHOME_THREAD_SINGLE */
static uint32_t last_millis{0};
static uint16_t millis_major{0};
#endif
// THREAD SAFETY NOTE:
// This function has three implementations, based on the precompiler flags
// - ESPHOME_THREAD_SINGLE - Runs on single-threaded platforms (ESP8266, etc.)
// This function has two out-of-line implementations, based on the precompiler flags:
// - ESPHOME_THREAD_MULTI_NO_ATOMICS - Runs on multi-threaded platforms without atomics (LibreTiny BK72xx)
// - ESPHOME_THREAD_MULTI_ATOMICS - Runs on multi-threaded platforms with atomics (LibreTiny RTL87xx/LN882x, etc.)
//
// The ESPHOME_THREAD_SINGLE path is inlined in time_64.h.
// Make sure all changes are synchronized if you edit this function.
//
// IMPORTANT: Always pass fresh millis() values to this function. The implementation
// handles out-of-order timestamps between threads, but minimizing time differences
// helps maintain accuracy.
#ifdef ESPHOME_THREAD_SINGLE
// Single-core platforms have no concurrency, so this is a simple implementation
// that just tracks 32-bit rollover (every 49.7 days) without any locking or atomics.
uint16_t major = millis_major;
uint32_t last = last_millis;
// Check for rollover
if (now < last && (last - now) > HALF_MAX_UINT32) {
millis_major++;
major++;
last_millis = now;
#ifdef ESPHOME_DEBUG_SCHEDULER
ESP_LOGD(TAG, "Detected true 32-bit rollover at %" PRIu32 "ms (was %" PRIu32 ")", now, last);
#endif /* ESPHOME_DEBUG_SCHEDULER */
} else if (now > last) {
// Only update if time moved forward
last_millis = now;
}
// Combine major (high 32 bits) and now (low 32 bits) into 64-bit time
return now + (static_cast<uint64_t>(major) << 32);
#elif defined(ESPHOME_THREAD_MULTI_NO_ATOMICS)
#if defined(ESPHOME_THREAD_MULTI_NO_ATOMICS)
// Without atomics, this implementation uses locks more aggressively:
// 1. Always locks when near the rollover boundary (within 10 seconds)
// 2. Always locks when detecting a large backwards jump
@@ -202,6 +182,8 @@ uint64_t Millis64Impl::compute(uint32_t now) {
#endif
}
#endif // !ESPHOME_THREAD_SINGLE
} // namespace esphome
#endif // !USE_NATIVE_64BIT_TIME
+32
View File
@@ -4,6 +4,9 @@
#ifndef USE_NATIVE_64BIT_TIME
#include <cstdint>
#include <limits>
#include "esphome/core/helpers.h"
namespace esphome {
@@ -16,7 +19,36 @@ class Millis64Impl {
friend uint64_t millis_64();
friend class Scheduler;
#ifdef ESPHOME_THREAD_SINGLE
// Storage defined in time_64.cpp — declared here so the inline body can access them.
static uint32_t last_millis_;
static uint16_t millis_major_;
static inline uint64_t ESPHOME_ALWAYS_INLINE compute(uint32_t now) {
// Half the 32-bit range - used to detect rollovers vs normal time progression
static constexpr uint32_t HALF_MAX_UINT32 = std::numeric_limits<uint32_t>::max() / 2;
// Single-core platforms have no concurrency, so this is a simple implementation
// that just tracks 32-bit rollover (every 49.7 days) without any locking or atomics.
uint16_t major = millis_major_;
uint32_t last = last_millis_;
// Check for rollover
if (now < last && (last - now) > HALF_MAX_UINT32) {
millis_major_++;
major++;
last_millis_ = now;
} else if (now > last) {
// Only update if time moved forward
last_millis_ = now;
}
// Combine major (high 32 bits) and now (low 32 bits) into 64-bit time
return now + (static_cast<uint64_t>(major) << 32);
}
#else
static uint64_t compute(uint32_t now);
#endif
};
} // namespace esphome