diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 8144435163..fbf68522aa 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -22,8 +22,17 @@ static const uint32_t MAX_LOGICALLY_DELETED_ITEMS = 10; // iterating over them from the loop task is fine; but iterating from any other context requires the lock to be held to // avoid the main thread modifying the list while it is being accessed. +void HOT Scheduler::set_timeout(Component *component, const char *name, uint32_t timeout, std::function func) { + return this->set_timeout_(component, name, timeout, func, false); +} + void HOT Scheduler::set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function func) { + return this->set_timeout_(component, name, timeout, func, true); +} + +void HOT Scheduler::set_timeout_(Component *component, const std::string &name, uint32_t timeout, + std::function func, bool make_copy) { const auto now = this->millis_(); if (!name.empty()) @@ -34,7 +43,7 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u auto item = make_unique(); item->component = component; - item->name = name; + item->set_name(name.c_str(), make_copy); item->type = SchedulerItem::TIMEOUT; item->next_execution_ = now + timeout; item->callback = std::move(func); @@ -49,6 +58,14 @@ bool HOT Scheduler::cancel_timeout(Component *component, const std::string &name } void HOT Scheduler::set_interval(Component *component, const std::string &name, uint32_t interval, std::function func) { + this->set_interval_(component, name, interval, func, true); +} +void HOT Scheduler::set_interval(Component *component, const char *name, uint32_t interval, + std::function func) { + this->set_interval_(component, name, interval, func, false); +} +void HOT Scheduler::set_interval_(Component *component, const std::string &name, uint32_t interval, + std::function func, bool make_copy) { const auto now = this->millis_(); if (!name.empty()) @@ -64,7 +81,7 @@ void HOT Scheduler::set_interval(Component *component, const std::string &name, auto item = make_unique(); item->component = component; - item->name = name; + item->set_name(name.c_str(), make_copy); item->type = SchedulerItem::INTERVAL; item->interval = interval; item->next_execution_ = now + offset; @@ -85,7 +102,7 @@ struct RetryArgs { uint8_t retry_countdown; uint32_t current_interval; Component *component; - std::string name; + std::string name; // Keep as std::string since retry uses it dynamically float backoff_increase_factor; Scheduler *scheduler; }; @@ -303,14 +320,16 @@ bool HOT Scheduler::cancel_item_(Component *component, const std::string &name, LockGuard guard{this->lock_}; bool ret = false; for (auto &it : this->items_) { - if (it->component == component && it->name == name && it->type == type && !it->remove) { + const char *item_name = it->get_name(); + if (it->component == component && item_name != nullptr && name == item_name && it->type == type && !it->remove) { to_remove_++; it->remove = true; ret = true; } } for (auto &it : this->to_add_) { - if (it->component == component && it->name == name && it->type == type) { + const char *item_name = it->get_name(); + if (it->component == component && item_name != nullptr && name == item_name && it->type == type) { it->remove = true; ret = true; } diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index 1284bcd4a7..80452d6628 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -12,11 +12,19 @@ class Component; class Scheduler { public: + // Public API - accepts std::string for backward compatibility void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function func); + void set_timeout(Component *component, const char *name, uint32_t timeout, std::function func); + void set_timeout_(Component *component, const std::string &name, uint32_t timeout, std::function func, + bool make_copy); + bool cancel_timeout(Component *component, const std::string &name); void set_interval(Component *component, const std::string &name, uint32_t interval, std::function func); - bool cancel_interval(Component *component, const std::string &name); + void set_interval(Component *component, const char *name, uint32_t interval, std::function func); + void set_interval_(Component *component, const std::string &name, uint32_t interval, std::function func, + bool make_copy); + bool cancel_interval(Component *component, const std::string &name); void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, std::function func, float backoff_increase_factor = 1.0f); bool cancel_retry(Component *component, const std::string &name); @@ -36,10 +44,65 @@ class Scheduler { // with a 16-bit rollover counter to create a 64-bit time that won't roll over for // billions of years. This ensures correct scheduling even when devices run for months. uint64_t next_execution_; - std::string name; + + // Optimized name storage using tagged union + union { + const char *static_name; // For string literals (no allocation) + char *dynamic_name; // For allocated strings + } name_; + std::function callback; - enum Type : uint8_t { TIMEOUT, INTERVAL } type; - bool remove; + + // Bit-packed fields to minimize padding + enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1; + bool remove : 1; + bool owns_name : 1; // True if name_.dynamic_name needs to be freed + // 5 bits padding + + // Constructor + SchedulerItem() + : component(nullptr), + interval(0), + next_execution_(0), + callback(nullptr), + type(TIMEOUT), + remove(false), + owns_name(false) { + name_.static_name = nullptr; + } + + // Destructor to clean up dynamic names + ~SchedulerItem() { + if (owns_name && name_.dynamic_name) { + delete[] name_.dynamic_name; + } + } + + // Helper to get the name regardless of storage type + const char *get_name() const { return owns_name ? name_.dynamic_name : name_.static_name; } + + // Helper to set name with proper ownership + void set_name(const char *name, bool make_copy = false) { + // Clean up old dynamic name if any + if (owns_name && name_.dynamic_name) { + delete[] name_.dynamic_name; + } + + if (name == nullptr || name[0] == '\0') { + name_.static_name = nullptr; + owns_name = false; + } else if (make_copy) { + // Make a copy for dynamic strings + size_t len = strlen(name); + name_.dynamic_name = new char[len + 1]; + strcpy(name_.dynamic_name, name); + owns_name = true; + } else { + // Use static string directly + name_.static_name = name; + owns_name = false; + } + } static bool cmp(const std::unique_ptr &a, const std::unique_ptr &b); const char *get_type_str() {