From dd2c5838e1c8d7560bc1f1f02e769fa904ebd388 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 06:25:55 -0600 Subject: [PATCH] [scheduler] Use enum class for InternalSchedulerID to avoid materialized constant The struct + constexpr namespace approach caused the compiler to materialize POLLING_UPDATE as a 4-byte symbol. An enum class with uint32_t underlying type gives the same type safety but enum values are true compile-time constants that never get materialized. --- esphome/core/base_automation.h | 9 +++++---- esphome/core/component.cpp | 4 ++-- esphome/core/component.h | 15 ++++----------- esphome/core/scheduler.h | 14 ++++++++------ 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index 8ecc9a70f1..67e1755cc9 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -192,15 +192,16 @@ template class DelayAction : public Action, public Compon if constexpr (sizeof...(Ts) == 0) { App.scheduler.set_timer_common_( this, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::NUMERIC_ID_INTERNAL, nullptr, - scheduler_internal_id::DELAY_ACTION.id, this->delay_.value(), [this]() { this->play_next_(); }, + static_cast(InternalSchedulerID::DELAY_ACTION), this->delay_.value(), + [this]() { this->play_next_(); }, /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1); } else { // For delays with arguments, use std::bind to preserve argument values // Arguments must be copied because original references may be invalid after delay auto f = std::bind(&DelayAction::play_next_, this, x...); App.scheduler.set_timer_common_(this, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::NUMERIC_ID_INTERNAL, - nullptr, scheduler_internal_id::DELAY_ACTION.id, this->delay_.value(x...), - std::move(f), + nullptr, static_cast(InternalSchedulerID::DELAY_ACTION), + this->delay_.value(x...), std::move(f), /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1); } } @@ -209,7 +210,7 @@ template class DelayAction : public Action, public Compon void play(const Ts &...x) override { /* ignore - see play_complex */ } - void stop() override { this->cancel_timeout(scheduler_internal_id::DELAY_ACTION); } + void stop() override { this->cancel_timeout(InternalSchedulerID::DELAY_ACTION); } }; template class LambdaAction : public Action { diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index f7b4404ae3..d43e50123c 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -528,12 +528,12 @@ void PollingComponent::call_setup() { void PollingComponent::start_poller() { // Register interval. - this->set_interval(scheduler_internal_id::POLLING_UPDATE, this->get_update_interval(), [this]() { this->update(); }); + this->set_interval(InternalSchedulerID::POLLING_UPDATE, this->get_update_interval(), [this]() { this->update(); }); } void PollingComponent::stop_poller() { // Clear the interval to suspend component - this->cancel_interval(scheduler_internal_id::POLLING_UPDATE); + this->cancel_interval(InternalSchedulerID::POLLING_UPDATE); } uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; } diff --git a/esphome/core/component.h b/esphome/core/component.h index 6f4dca9cf6..0adcef59e6 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -49,21 +49,14 @@ extern const float LATE; static const uint32_t SCHEDULER_DONT_RUN = 4294967295UL; -/// Type-safe wrapper for internal scheduler IDs used by core base classes. +/// Type-safe scheduler IDs for core base classes. /// Uses a separate NameType (NUMERIC_ID_INTERNAL) so IDs can never collide /// with component-level NUMERIC_ID values, even if the uint32_t values overlap. -struct InternalSchedulerID { - uint32_t id; +enum class InternalSchedulerID : uint32_t { + POLLING_UPDATE = 0, // PollingComponent interval + DELAY_ACTION = 1, // DelayAction timeout }; -/// Reserved scheduler IDs for core base classes. -/// These use InternalSchedulerID which routes through NUMERIC_ID_INTERNAL, -/// a separate matching namespace from the NUMERIC_ID used by components. -namespace scheduler_internal_id { -constexpr InternalSchedulerID POLLING_UPDATE{0}; // PollingComponent interval -constexpr InternalSchedulerID DELAY_ACTION{1}; // DelayAction timeout -} // namespace scheduler_internal_id - // Forward declaration class PollingComponent; diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index ed26eff27d..972c00705f 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -48,8 +48,8 @@ class Scheduler { void set_timeout(Component *component, uint32_t id, uint32_t timeout, std::function func); /// Set a timeout with an internal scheduler ID (separate namespace from component NUMERIC_ID) void set_timeout(Component *component, InternalSchedulerID id, uint32_t timeout, std::function func) { - this->set_timer_common_(component, SchedulerItem::TIMEOUT, NameType::NUMERIC_ID_INTERNAL, nullptr, id.id, timeout, - std::move(func)); + this->set_timer_common_(component, SchedulerItem::TIMEOUT, NameType::NUMERIC_ID_INTERNAL, nullptr, + static_cast(id), timeout, std::move(func)); } ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") @@ -57,7 +57,8 @@ class Scheduler { bool cancel_timeout(Component *component, const char *name); bool cancel_timeout(Component *component, uint32_t id); bool cancel_timeout(Component *component, InternalSchedulerID id) { - return this->cancel_item_(component, NameType::NUMERIC_ID_INTERNAL, nullptr, id.id, SchedulerItem::TIMEOUT); + return this->cancel_item_(component, NameType::NUMERIC_ID_INTERNAL, nullptr, static_cast(id), + SchedulerItem::TIMEOUT); } ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") @@ -76,8 +77,8 @@ class Scheduler { void set_interval(Component *component, uint32_t id, uint32_t interval, std::function func); /// Set an interval with an internal scheduler ID (separate namespace from component NUMERIC_ID) void set_interval(Component *component, InternalSchedulerID id, uint32_t interval, std::function func) { - this->set_timer_common_(component, SchedulerItem::INTERVAL, NameType::NUMERIC_ID_INTERNAL, nullptr, id.id, interval, - std::move(func)); + this->set_timer_common_(component, SchedulerItem::INTERVAL, NameType::NUMERIC_ID_INTERNAL, nullptr, + static_cast(id), interval, std::move(func)); } ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") @@ -85,7 +86,8 @@ class Scheduler { bool cancel_interval(Component *component, const char *name); bool cancel_interval(Component *component, uint32_t id); bool cancel_interval(Component *component, InternalSchedulerID id) { - return this->cancel_item_(component, NameType::NUMERIC_ID_INTERNAL, nullptr, id.id, SchedulerItem::INTERVAL); + return this->cancel_item_(component, NameType::NUMERIC_ID_INTERNAL, nullptr, static_cast(id), + SchedulerItem::INTERVAL); } ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")