1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-08 00:31:58 +00:00

[text_sensor] Use in-place mutation for filters to reduce heap allocations (#13475)

This commit is contained in:
J. Nick Koston
2026-01-27 17:33:46 -10:00
committed by GitHub
parent f73c539ea7
commit fe6f27c526
2 changed files with 51 additions and 42 deletions

View File

@@ -9,19 +9,18 @@ namespace text_sensor {
static const char *const TAG = "text_sensor.filter";
// Filter
void Filter::input(const std::string &value) {
void Filter::input(std::string value) {
ESP_LOGVV(TAG, "Filter(%p)::input(%s)", this, value.c_str());
optional<std::string> out = this->new_value(value);
if (out.has_value())
this->output(*out);
if (this->new_value(value))
this->output(value);
}
void Filter::output(const std::string &value) {
void Filter::output(std::string &value) {
if (this->next_ == nullptr) {
ESP_LOGVV(TAG, "Filter(%p)::output(%s) -> SENSOR", this, value.c_str());
this->parent_->internal_send_state_to_frontend(value);
} else {
ESP_LOGVV(TAG, "Filter(%p)::output(%s) -> %p", this, value.c_str(), this->next_);
this->next_->input(value);
this->next_->input(std::move(value));
}
}
void Filter::initialize(TextSensor *parent, Filter *next) {
@@ -35,43 +34,48 @@ LambdaFilter::LambdaFilter(lambda_filter_t lambda_filter) : lambda_filter_(std::
const lambda_filter_t &LambdaFilter::get_lambda_filter() const { return this->lambda_filter_; }
void LambdaFilter::set_lambda_filter(const lambda_filter_t &lambda_filter) { this->lambda_filter_ = lambda_filter; }
optional<std::string> LambdaFilter::new_value(std::string value) {
auto it = this->lambda_filter_(value);
ESP_LOGVV(TAG, "LambdaFilter(%p)::new_value(%s) -> %s", this, value.c_str(), it.value_or("").c_str());
return it;
bool LambdaFilter::new_value(std::string &value) {
auto result = this->lambda_filter_(value);
if (result.has_value()) {
ESP_LOGVV(TAG, "LambdaFilter(%p)::new_value(%s) -> %s (continue)", this, value.c_str(), result->c_str());
value = std::move(*result);
return true;
}
ESP_LOGVV(TAG, "LambdaFilter(%p)::new_value(%s) -> (stop)", this, value.c_str());
return false;
}
// ToUpperFilter
optional<std::string> ToUpperFilter::new_value(std::string value) {
bool ToUpperFilter::new_value(std::string &value) {
for (char &c : value)
c = ::toupper(c);
return value;
return true;
}
// ToLowerFilter
optional<std::string> ToLowerFilter::new_value(std::string value) {
bool ToLowerFilter::new_value(std::string &value) {
for (char &c : value)
c = ::tolower(c);
return value;
return true;
}
// Append
optional<std::string> AppendFilter::new_value(std::string value) {
bool AppendFilter::new_value(std::string &value) {
value.append(this->suffix_);
return value;
return true;
}
// Prepend
optional<std::string> PrependFilter::new_value(std::string value) {
bool PrependFilter::new_value(std::string &value) {
value.insert(0, this->prefix_);
return value;
return true;
}
// Substitute
SubstituteFilter::SubstituteFilter(const std::initializer_list<Substitution> &substitutions)
: substitutions_(substitutions) {}
optional<std::string> SubstituteFilter::new_value(std::string value) {
bool SubstituteFilter::new_value(std::string &value) {
for (const auto &sub : this->substitutions_) {
// Compute lengths once per substitution (strlen is fast, called infrequently)
const size_t from_len = strlen(sub.from);
@@ -84,20 +88,20 @@ optional<std::string> SubstituteFilter::new_value(std::string value) {
pos += to_len;
}
}
return value;
return true;
}
// Map
MapFilter::MapFilter(const std::initializer_list<Substitution> &mappings) : mappings_(mappings) {}
optional<std::string> MapFilter::new_value(std::string value) {
bool MapFilter::new_value(std::string &value) {
for (const auto &mapping : this->mappings_) {
if (value == mapping.from) {
value.assign(mapping.to);
return value;
return true;
}
}
return value; // Pass through if no match
return true; // Pass through if no match
}
} // namespace text_sensor

View File

@@ -17,21 +17,20 @@ class Filter {
public:
/** This will be called every time the filter receives a new value.
*
* It can return an empty optional to indicate that the filter chain
* should stop, otherwise the value in the filter will be passed down
* the chain.
* Modify the value in place. Return false to stop the filter chain
* (value will not be published), or true to continue.
*
* @param value The new value.
* @return An optional string, the new value that should be pushed out.
* @param value The value to filter (modified in place).
* @return True to continue the filter chain, false to stop.
*/
virtual optional<std::string> new_value(std::string value) = 0;
virtual bool new_value(std::string &value) = 0;
/// Initialize this filter, please note this can be called more than once.
virtual void initialize(TextSensor *parent, Filter *next);
void input(const std::string &value);
void input(std::string value);
void output(const std::string &value);
void output(std::string &value);
protected:
friend TextSensor;
@@ -45,15 +44,14 @@ using lambda_filter_t = std::function<optional<std::string>(std::string)>;
/** This class allows for creation of simple template filters.
*
* The constructor accepts a lambda of the form std::string -> optional<std::string>.
* It will be called with each new value in the filter chain and returns the modified
* value that shall be passed down the filter chain. Returning an empty Optional
* means that the value shall be discarded.
* Return a modified string to continue the chain, or return {} to stop
* (value will not be published).
*/
class LambdaFilter : public Filter {
public:
explicit LambdaFilter(lambda_filter_t lambda_filter);
optional<std::string> new_value(std::string value) override;
bool new_value(std::string &value) override;
const lambda_filter_t &get_lambda_filter() const;
void set_lambda_filter(const lambda_filter_t &lambda_filter);
@@ -71,7 +69,14 @@ class StatelessLambdaFilter : public Filter {
public:
explicit StatelessLambdaFilter(optional<std::string> (*lambda_filter)(std::string)) : lambda_filter_(lambda_filter) {}
optional<std::string> new_value(std::string value) override { return this->lambda_filter_(value); }
bool new_value(std::string &value) override {
auto result = this->lambda_filter_(value);
if (result.has_value()) {
value = std::move(*result);
return true;
}
return false;
}
protected:
optional<std::string> (*lambda_filter_)(std::string);
@@ -80,20 +85,20 @@ class StatelessLambdaFilter : public Filter {
/// A simple filter that converts all text to uppercase
class ToUpperFilter : public Filter {
public:
optional<std::string> new_value(std::string value) override;
bool new_value(std::string &value) override;
};
/// A simple filter that converts all text to lowercase
class ToLowerFilter : public Filter {
public:
optional<std::string> new_value(std::string value) override;
bool new_value(std::string &value) override;
};
/// A simple filter that adds a string to the end of another string
class AppendFilter : public Filter {
public:
explicit AppendFilter(const char *suffix) : suffix_(suffix) {}
optional<std::string> new_value(std::string value) override;
bool new_value(std::string &value) override;
protected:
const char *suffix_;
@@ -103,7 +108,7 @@ class AppendFilter : public Filter {
class PrependFilter : public Filter {
public:
explicit PrependFilter(const char *prefix) : prefix_(prefix) {}
optional<std::string> new_value(std::string value) override;
bool new_value(std::string &value) override;
protected:
const char *prefix_;
@@ -118,7 +123,7 @@ struct Substitution {
class SubstituteFilter : public Filter {
public:
explicit SubstituteFilter(const std::initializer_list<Substitution> &substitutions);
optional<std::string> new_value(std::string value) override;
bool new_value(std::string &value) override;
protected:
FixedVector<Substitution> substitutions_;
@@ -151,7 +156,7 @@ class SubstituteFilter : public Filter {
class MapFilter : public Filter {
public:
explicit MapFilter(const std::initializer_list<Substitution> &mappings);
optional<std::string> new_value(std::string value) override;
bool new_value(std::string &value) override;
protected:
FixedVector<Substitution> mappings_;