diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 869981eb7a..d03326ccf8 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -347,27 +347,26 @@ template class StatefulEntityBase : public EntityBase { virtual bool set_new_state(const optional &new_state) { // Access flags_ directly to avoid function call overhead in this hot path bool had_state = this->flags_.has_state; - // Cache get_state() result to avoid calling the virtual method twice - T current{}; - if (had_state) - current = this->get_state(); + // Use pointer to avoid requiring T to be default-constructible + const T *current = had_state ? &this->get_state() : nullptr; if (new_state.has_value()) { - if (had_state && current == new_state.value()) + if (current != nullptr && *current == new_state.value()) return false; // same value, no change } else if (!had_state) { return false; // already invalidated, no change } - // State changed — update storage before firing callbacks so callback code - // can inspect the entity's current state via get_state()/has_state() + // Capture old_state before set_state_value — current pointer aliases subclass storage + bool has_full_cbs = !this->full_state_callbacks_.empty(); + optional old_state; + if (has_full_cbs) + old_state = current != nullptr ? optional(*current) : nullopt; + // Update storage before firing callbacks so callback code can inspect current state this->flags_.has_state = new_state.has_value(); if (new_state.has_value()) { this->set_state_value(new_state.value()); } - // Only construct old_state and call full_state_callbacks when callbacks are registered - if (!this->full_state_callbacks_.empty()) { - optional old_state = had_state ? optional(current) : nullopt; + if (has_full_cbs) this->full_state_callbacks_.call(old_state, new_state); - } // had_state first: on every change except the first, skips the virtual call if (new_state.has_value() && (had_state || this->get_trigger_on_initial_state())) this->state_callbacks_.call(new_state.value());