mirror of
https://github.com/esphome/esphome.git
synced 2026-06-02 19:18:20 +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->invalidate_state();
|
||||||
this->publish_state(new_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) {
|
bool BinarySensor::set_new_state(const optional<bool> &new_state) {
|
||||||
if (StatefulEntityBase::set_new_state(new_state)) {
|
if (StatefulEntityBase::set_new_state(new_state)) {
|
||||||
// weirdly, this file could be compiled even without USE_BINARY_SENSOR defined
|
// 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> {
|
class BinarySensor : public StatefulEntityBase<bool> {
|
||||||
public:
|
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.
|
/** Publish a new state to the front-end.
|
||||||
*
|
*
|
||||||
@@ -54,16 +57,24 @@ class BinarySensor : public StatefulEntityBase<bool> {
|
|||||||
|
|
||||||
// ========== INTERNAL METHODS ==========
|
// ========== INTERNAL METHODS ==========
|
||||||
// (In most use cases you won't need these)
|
// (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.
|
/// Return whether this binary sensor has outputted a state.
|
||||||
virtual bool is_status_binary_sensor() const;
|
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{};
|
bool state{};
|
||||||
|
|
||||||
protected:
|
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
|
#ifdef USE_BINARY_SENSOR_FILTER
|
||||||
Filter *filter_list_{nullptr};
|
Filter *filter_list_{nullptr};
|
||||||
#endif
|
#endif
|
||||||
@@ -73,7 +84,7 @@ class BinarySensor : public StatefulEntityBase<bool> {
|
|||||||
|
|
||||||
class BinarySensorInitiallyOff : public BinarySensor {
|
class BinarySensorInitiallyOff : public BinarySensor {
|
||||||
public:
|
public:
|
||||||
bool has_state() const override { return true; }
|
BinarySensorInitiallyOff() { this->set_has_state(true); }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::binary_sensor
|
} // namespace esphome::binary_sensor
|
||||||
|
|||||||
+60
-26
@@ -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)
|
#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);
|
void log_entity_unit_of_measurement(const char *tag, const char *prefix, const EntityBase &obj);
|
||||||
|
|
||||||
/**
|
/** Base class for entities that track a typed state value with change-detection and callbacks.
|
||||||
* An entity that has a state.
|
*
|
||||||
* @tparam T The type of the state
|
* 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 {
|
template<typename T> class StatefulEntityBase : public EntityBase {
|
||||||
public:
|
public:
|
||||||
virtual bool has_state() const { return this->state_.has_value(); }
|
/// Return the current state value. Only valid when has_state() is true.
|
||||||
virtual const T &get_state() const { return this->state_.value(); } // NOLINT(bugprone-unchecked-optional-access)
|
virtual const T &get_state() const = 0;
|
||||||
virtual T get_state_default(T default_value) const { return this->state_.value_or(default_value); }
|
/// 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({}); }
|
void invalidate_state() { this->set_new_state({}); }
|
||||||
|
|
||||||
template<typename F> void add_full_state_callback(F &&callback) {
|
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));
|
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:
|
protected:
|
||||||
optional<T> state_{};
|
/// Subclasses return whether callbacks should fire on the very first state.
|
||||||
/**
|
virtual bool get_trigger_on_initial_state() const = 0;
|
||||||
* Set a new state for this entity. This will trigger callbacks only if the new state is different from the previous.
|
|
||||||
|
/** Apply a new state, de-duplicating and firing callbacks as needed.
|
||||||
*
|
*
|
||||||
* @param new_state The new state.
|
* Pass nullopt to invalidate (clear) the state. Pass a value to set it.
|
||||||
* @return True if the state was changed, false if it was the same as before.
|
* 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) {
|
virtual bool set_new_state(const optional<T> &new_state) {
|
||||||
if (this->state_ != new_state) {
|
// Access flags_ directly to avoid function call overhead in this hot path
|
||||||
// call the full state callbacks with the previous and new state
|
bool had_state = this->flags_.has_state;
|
||||||
this->full_state_callbacks_.call(this->state_, new_state);
|
// Use pointer to avoid requiring T to be default-constructible
|
||||||
// trigger legacy callbacks only if the new state is valid and either the trigger on initial state is enabled or
|
const T *current = had_state ? &this->get_state() : nullptr;
|
||||||
// the previous state was valid
|
if (new_state.has_value()) {
|
||||||
auto had_state = this->has_state();
|
if (current != nullptr && *current == new_state.value())
|
||||||
this->state_ = new_state;
|
return false; // same value, no change
|
||||||
if (new_state.has_value() && (this->trigger_on_initial_state_ || had_state))
|
} else if (!had_state) {
|
||||||
|
return false; // already invalidated, no change
|
||||||
|
}
|
||||||
|
// 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());
|
this->state_callbacks_.call(new_state.value());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
/// Subclasses implement this to store the actual value into their own storage.
|
||||||
}
|
virtual void set_state_value(const T &value) = 0;
|
||||||
bool trigger_on_initial_state_{true};
|
|
||||||
LazyCallbackManager<void(optional<T> previous, optional<T> current)> full_state_callbacks_;
|
LazyCallbackManager<void(optional<T> previous, optional<T> current)> full_state_callbacks_;
|
||||||
LazyCallbackManager<void(T)> state_callbacks_;
|
LazyCallbackManager<void(T)> state_callbacks_;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user