mirror of
https://github.com/esphome/esphome.git
synced 2026-06-01 09:25:09 +08:00
[binary_sensor] Remove redundant optional<bool> state_, save 8 bytes per instance (#15095)
This commit is contained in:
@@ -32,13 +32,6 @@ void BinarySensor::publish_initial_state(bool new_state) {
|
||||
this->invalidate_state();
|
||||
this->publish_state(new_state);
|
||||
}
|
||||
void BinarySensor::send_state_internal(bool new_state) {
|
||||
// copy the new state to the visible property for backwards compatibility, before any callbacks
|
||||
this->state = new_state;
|
||||
// Note that set_new_state_ de-dups and will only trigger callbacks if the state has actually changed
|
||||
this->set_new_state(new_state);
|
||||
}
|
||||
|
||||
bool BinarySensor::set_new_state(const optional<bool> &new_state) {
|
||||
if (StatefulEntityBase::set_new_state(new_state)) {
|
||||
// weirdly, this file could be compiled even without USE_BINARY_SENSOR defined
|
||||
|
||||
@@ -32,7 +32,10 @@ void log_binary_sensor(const char *tag, const char *prefix, const char *type, Bi
|
||||
*/
|
||||
class BinarySensor : public StatefulEntityBase<bool> {
|
||||
public:
|
||||
explicit BinarySensor(){};
|
||||
explicit BinarySensor() = default;
|
||||
|
||||
const bool &get_state() const override { return this->state; }
|
||||
void set_trigger_on_initial_state(bool value) { this->trigger_on_initial_state_ = value; }
|
||||
|
||||
/** Publish a new state to the front-end.
|
||||
*
|
||||
@@ -54,16 +57,24 @@ class BinarySensor : public StatefulEntityBase<bool> {
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void send_state_internal(bool new_state);
|
||||
void send_state_internal(bool new_state) {
|
||||
// Fast path: skip virtual dispatch when state hasn't changed
|
||||
if (this->flags_.has_state && this->state == new_state)
|
||||
return;
|
||||
this->set_new_state(new_state);
|
||||
}
|
||||
|
||||
/// Return whether this binary sensor has outputted a state.
|
||||
virtual bool is_status_binary_sensor() const;
|
||||
|
||||
// For backward compatibility, provide an accessible property
|
||||
|
||||
/// The current state of this binary sensor. Also used as the backing storage for StatefulEntityBase.
|
||||
bool state{};
|
||||
|
||||
protected:
|
||||
bool get_trigger_on_initial_state() const override { return this->trigger_on_initial_state_; }
|
||||
void set_state_value(const bool &value) override { this->state = value; }
|
||||
|
||||
bool trigger_on_initial_state_{true};
|
||||
#ifdef USE_BINARY_SENSOR_FILTER
|
||||
Filter *filter_list_{nullptr};
|
||||
#endif
|
||||
@@ -73,7 +84,7 @@ class BinarySensor : public StatefulEntityBase<bool> {
|
||||
|
||||
class BinarySensorInitiallyOff : public BinarySensor {
|
||||
public:
|
||||
bool has_state() const override { return true; }
|
||||
BinarySensorInitiallyOff() { this->set_has_state(true); }
|
||||
};
|
||||
|
||||
} // namespace esphome::binary_sensor
|
||||
|
||||
+61
-27
@@ -296,15 +296,36 @@ void log_entity_device_class(const char *tag, const char *prefix, const EntityBa
|
||||
#define LOG_ENTITY_UNIT_OF_MEASUREMENT(tag, prefix, obj) log_entity_unit_of_measurement(tag, prefix, obj)
|
||||
void log_entity_unit_of_measurement(const char *tag, const char *prefix, const EntityBase &obj);
|
||||
|
||||
/**
|
||||
* An entity that has a state.
|
||||
* @tparam T The type of the state
|
||||
/** Base class for entities that track a typed state value with change-detection and callbacks.
|
||||
*
|
||||
* This class does not store the state value — subclasses own their storage. Whether a state
|
||||
* has been set is tracked by EntityBase::has_state().
|
||||
*
|
||||
* Subclasses must implement:
|
||||
* - get_state(): return a const reference to the current value
|
||||
* - set_state_value(): store a new value (called only when the state actually changes)
|
||||
* - get_trigger_on_initial_state(): return whether callbacks should fire on the first state
|
||||
*
|
||||
* Subclasses may override set_new_state() to add behavior (logging, notifications) after calling
|
||||
* the base implementation. Since set_new_state() is virtual, callers like invalidate_state()
|
||||
* dispatch through the vtable to the subclass override in the .cpp, avoiding template code
|
||||
* bloat at inline call sites. Subclasses may also add a fast-path dedup check before calling
|
||||
* set_new_state() to skip virtual dispatch entirely when the state hasn't changed.
|
||||
*
|
||||
* Callback behavior:
|
||||
* - full_state_callbacks_: fired on every change, receives optional<T> previous and current
|
||||
* - state_callbacks_: fired only when the new state has a value, and either this is not the
|
||||
* first state (had_state) or trigger_on_initial_state is set
|
||||
*
|
||||
* @tparam T The type of the state value
|
||||
*/
|
||||
template<typename T> class StatefulEntityBase : public EntityBase {
|
||||
public:
|
||||
virtual bool has_state() const { return this->state_.has_value(); }
|
||||
virtual const T &get_state() const { return this->state_.value(); } // NOLINT(bugprone-unchecked-optional-access)
|
||||
virtual T get_state_default(T default_value) const { return this->state_.value_or(default_value); }
|
||||
/// Return the current state value. Only valid when has_state() is true.
|
||||
virtual const T &get_state() const = 0;
|
||||
/// Return the current state if available, otherwise return the provided default.
|
||||
T get_state_default(T default_value) const { return this->has_state() ? this->get_state() : default_value; }
|
||||
/// Clear the state — sets has_state() to false and fires callbacks with nullopt.
|
||||
void invalidate_state() { this->set_new_state({}); }
|
||||
|
||||
template<typename F> void add_full_state_callback(F &&callback) {
|
||||
@@ -314,33 +335,46 @@ template<typename T> class StatefulEntityBase : public EntityBase {
|
||||
this->state_callbacks_.add(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
void set_trigger_on_initial_state(bool trigger_on_initial_state) {
|
||||
this->trigger_on_initial_state_ = trigger_on_initial_state;
|
||||
}
|
||||
|
||||
protected:
|
||||
optional<T> state_{};
|
||||
/**
|
||||
* Set a new state for this entity. This will trigger callbacks only if the new state is different from the previous.
|
||||
/// Subclasses return whether callbacks should fire on the very first state.
|
||||
virtual bool get_trigger_on_initial_state() const = 0;
|
||||
|
||||
/** Apply a new state, de-duplicating and firing callbacks as needed.
|
||||
*
|
||||
* @param new_state The new state.
|
||||
* @return True if the state was changed, false if it was the same as before.
|
||||
* Pass nullopt to invalidate (clear) the state. Pass a value to set it.
|
||||
* Returns true if the state actually changed, false if it was the same.
|
||||
* Subclasses may override to add logging/notifications after calling the base.
|
||||
*/
|
||||
virtual bool set_new_state(const optional<T> &new_state) {
|
||||
if (this->state_ != new_state) {
|
||||
// call the full state callbacks with the previous and new state
|
||||
this->full_state_callbacks_.call(this->state_, new_state);
|
||||
// trigger legacy callbacks only if the new state is valid and either the trigger on initial state is enabled or
|
||||
// the previous state was valid
|
||||
auto had_state = this->has_state();
|
||||
this->state_ = new_state;
|
||||
if (new_state.has_value() && (this->trigger_on_initial_state_ || had_state))
|
||||
this->state_callbacks_.call(new_state.value());
|
||||
return true;
|
||||
// Access flags_ directly to avoid function call overhead in this hot path
|
||||
bool had_state = this->flags_.has_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 (current != nullptr && *current == new_state.value())
|
||||
return false; // same value, no change
|
||||
} else if (!had_state) {
|
||||
return false; // already invalidated, no change
|
||||
}
|
||||
return false;
|
||||
// Capture old_state before set_state_value — current pointer aliases subclass storage
|
||||
bool has_full_cbs = !this->full_state_callbacks_.empty();
|
||||
optional<T> old_state;
|
||||
if (has_full_cbs)
|
||||
old_state = current != nullptr ? optional<T>(*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());
|
||||
}
|
||||
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());
|
||||
return true;
|
||||
}
|
||||
bool trigger_on_initial_state_{true};
|
||||
/// Subclasses implement this to store the actual value into their own storage.
|
||||
virtual void set_state_value(const T &value) = 0;
|
||||
LazyCallbackManager<void(optional<T> previous, optional<T> current)> full_state_callbacks_;
|
||||
LazyCallbackManager<void(T)> state_callbacks_;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user