Merge branch 'empty_hidden_side_effects' into integration

This commit is contained in:
J. Nick Koston 2025-07-20 12:18:14 -10:00
commit 8becc57835
No known key found for this signature in database
6 changed files with 29 additions and 25 deletions

View File

@ -3,6 +3,7 @@ import esphome.codegen as cg
from esphome.components import display, spi from esphome.components import display, spi
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_FLIP_X,
CONF_ID, CONF_ID,
CONF_INTENSITY, CONF_INTENSITY,
CONF_LAMBDA, CONF_LAMBDA,
@ -14,7 +15,6 @@ CODEOWNERS = ["@rspaargaren"]
DEPENDENCIES = ["spi"] DEPENDENCIES = ["spi"]
CONF_ROTATE_CHIP = "rotate_chip" CONF_ROTATE_CHIP = "rotate_chip"
CONF_FLIP_X = "flip_x"
CONF_SCROLL_SPEED = "scroll_speed" CONF_SCROLL_SPEED = "scroll_speed"
CONF_SCROLL_DWELL = "scroll_dwell" CONF_SCROLL_DWELL = "scroll_dwell"
CONF_SCROLL_DELAY = "scroll_delay" CONF_SCROLL_DELAY = "scroll_delay"

View File

@ -6,6 +6,8 @@ from esphome.const import (
CONF_BRIGHTNESS, CONF_BRIGHTNESS,
CONF_CONTRAST, CONF_CONTRAST,
CONF_EXTERNAL_VCC, CONF_EXTERNAL_VCC,
CONF_FLIP_X,
CONF_FLIP_Y,
CONF_INVERT, CONF_INVERT,
CONF_LAMBDA, CONF_LAMBDA,
CONF_MODEL, CONF_MODEL,
@ -18,9 +20,6 @@ ssd1306_base_ns = cg.esphome_ns.namespace("ssd1306_base")
SSD1306 = ssd1306_base_ns.class_("SSD1306", cg.PollingComponent, display.DisplayBuffer) SSD1306 = ssd1306_base_ns.class_("SSD1306", cg.PollingComponent, display.DisplayBuffer)
SSD1306Model = ssd1306_base_ns.enum("SSD1306Model") SSD1306Model = ssd1306_base_ns.enum("SSD1306Model")
CONF_FLIP_X = "flip_x"
CONF_FLIP_Y = "flip_y"
MODELS = { MODELS = {
"SSD1306_128X32": SSD1306Model.SSD1306_MODEL_128_32, "SSD1306_128X32": SSD1306Model.SSD1306_MODEL_128_32,
"SSD1306_128X64": SSD1306Model.SSD1306_MODEL_128_64, "SSD1306_128X64": SSD1306Model.SSD1306_MODEL_128_64,

View File

@ -383,6 +383,8 @@ CONF_FINGER_ID = "finger_id"
CONF_FINGERPRINT_COUNT = "fingerprint_count" CONF_FINGERPRINT_COUNT = "fingerprint_count"
CONF_FLASH_LENGTH = "flash_length" CONF_FLASH_LENGTH = "flash_length"
CONF_FLASH_TRANSITION_LENGTH = "flash_transition_length" CONF_FLASH_TRANSITION_LENGTH = "flash_transition_length"
CONF_FLIP_X = "flip_x"
CONF_FLIP_Y = "flip_y"
CONF_FLOW = "flow" CONF_FLOW = "flow"
CONF_FLOW_CONTROL_PIN = "flow_control_pin" CONF_FLOW_CONTROL_PIN = "flow_control_pin"
CONF_FONT = "font" CONF_FONT = "font"

View File

@ -219,10 +219,13 @@ bool HOT Scheduler::cancel_retry(Component *component, const std::string &name)
optional<uint32_t> HOT Scheduler::next_schedule_in(uint32_t now) { optional<uint32_t> HOT Scheduler::next_schedule_in(uint32_t now) {
// IMPORTANT: This method should only be called from the main thread (loop task). // IMPORTANT: This method should only be called from the main thread (loop task).
// It calls empty_() and accesses items_[0] without holding a lock, which is only // It performs cleanup and accesses items_[0] without holding a lock, which is only
// safe when called from the main thread. Other threads must not call this method. // safe when called from the main thread. Other threads must not call this method.
if (this->empty_())
// If no items, return empty optional
if (this->cleanup_() == 0)
return {}; return {};
auto &item = this->items_[0]; auto &item = this->items_[0];
// Convert the fresh timestamp from caller (usually Application::loop()) to 64-bit // Convert the fresh timestamp from caller (usually Application::loop()) to 64-bit
const auto now_64 = this->millis_64_(now); // 'now' from parameter - fresh from caller const auto now_64 = this->millis_64_(now); // 'now' from parameter - fresh from caller
@ -282,7 +285,9 @@ void HOT Scheduler::call(uint32_t now) {
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(), now_64, ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(), now_64,
this->millis_major_, this->last_millis_); this->millis_major_, this->last_millis_);
#endif /* else ESPHOME_CORES_MULTI_ATOMICS */ #endif /* else ESPHOME_CORES_MULTI_ATOMICS */
while (!this->empty_()) { // Cleanup before debug output
this->cleanup_();
while (!this->items_.empty()) {
std::unique_ptr<SchedulerItem> item; std::unique_ptr<SchedulerItem> item;
{ {
LockGuard guard{this->lock_}; LockGuard guard{this->lock_};
@ -333,7 +338,9 @@ void HOT Scheduler::call(uint32_t now) {
this->to_remove_ = 0; this->to_remove_ = 0;
} }
while (!this->empty_()) { // Cleanup removed items before processing
this->cleanup_();
while (!this->items_.empty()) {
// use scoping to indicate visibility of `item` variable // use scoping to indicate visibility of `item` variable
{ {
// Don't copy-by value yet // Don't copy-by value yet
@ -399,8 +406,8 @@ void HOT Scheduler::process_to_add() {
} }
this->to_add_.clear(); this->to_add_.clear();
} }
void HOT Scheduler::cleanup_() { size_t HOT Scheduler::cleanup_() {
// Fast path: if nothing to remove, just return // Fast path: if nothing to remove, just return the current size
// Reading to_remove_ without lock is safe because: // Reading to_remove_ without lock is safe because:
// 1. We only call this from the main thread during call() // 1. We only call this from the main thread during call()
// 2. If it's 0, there's definitely nothing to cleanup // 2. If it's 0, there's definitely nothing to cleanup
@ -408,7 +415,7 @@ void HOT Scheduler::cleanup_() {
// 4. Not all platforms support atomics, so we accept this race in favor of performance // 4. Not all platforms support atomics, so we accept this race in favor of performance
// 5. The worst case is a one-loop-iteration delay in cleanup, which is harmless // 5. The worst case is a one-loop-iteration delay in cleanup, which is harmless
if (this->to_remove_ == 0) if (this->to_remove_ == 0)
return; return this->items_.size();
// We must hold the lock for the entire cleanup operation because: // We must hold the lock for the entire cleanup operation because:
// 1. We're modifying items_ (via pop_raw_) which requires exclusive access // 1. We're modifying items_ (via pop_raw_) which requires exclusive access
@ -422,10 +429,11 @@ void HOT Scheduler::cleanup_() {
while (!this->items_.empty()) { while (!this->items_.empty()) {
auto &item = this->items_[0]; auto &item = this->items_[0];
if (!item->remove) if (!item->remove)
return; break;
this->to_remove_--; this->to_remove_--;
this->pop_raw_(); this->pop_raw_();
} }
return this->items_.size();
} }
void HOT Scheduler::pop_raw_() { void HOT Scheduler::pop_raw_() {
std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp); std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);

View File

@ -58,6 +58,9 @@ class Scheduler {
// Calculate when the next scheduled item should run // Calculate when the next scheduled item should run
// @param now Fresh timestamp from millis() - must not be stale/cached // @param now Fresh timestamp from millis() - must not be stale/cached
// Returns the time in milliseconds until the next scheduled item, or nullopt if no items
// This method performs cleanup of removed items before checking the schedule
// IMPORTANT: This method should only be called from the main thread (loop task).
optional<uint32_t> next_schedule_in(uint32_t now); optional<uint32_t> next_schedule_in(uint32_t now);
// Execute all scheduled items that are ready // Execute all scheduled items that are ready
@ -147,7 +150,10 @@ class Scheduler {
uint32_t delay, std::function<void()> func); uint32_t delay, std::function<void()> func);
uint64_t millis_64_(uint32_t now); uint64_t millis_64_(uint32_t now);
void cleanup_(); // Cleanup logically deleted items from the scheduler
// Returns the number of items remaining after cleanup
// IMPORTANT: This method should only be called from the main thread (loop task).
size_t cleanup_();
void pop_raw_(); void pop_raw_();
private: private:
@ -191,17 +197,6 @@ class Scheduler {
return item->remove || (item->component != nullptr && item->component->is_failed()); return item->remove || (item->component != nullptr && item->component->is_failed());
} }
// Check if the scheduler has no items.
// IMPORTANT: This method should only be called from the main thread (loop task).
// It performs cleanup of removed items and checks if the queue is empty.
// The items_.empty() check at the end is done without a lock for performance,
// which is safe because this is only called from the main thread while other
// threads only add items (never remove them).
bool empty_() {
this->cleanup_();
return this->items_.empty();
}
Mutex lock_; Mutex lock_;
std::vector<std::unique_ptr<SchedulerItem>> items_; std::vector<std::unique_ptr<SchedulerItem>> items_;
std::vector<std::unique_ptr<SchedulerItem>> to_add_; std::vector<std::unique_ptr<SchedulerItem>> to_add_;

View File

@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
esptool==4.9.0 esptool==4.9.0
click==8.1.7 click==8.1.7
esphome-dashboard==20250514.0 esphome-dashboard==20250514.0
aioesphomeapi==37.0.1 aioesphomeapi==37.0.2
zeroconf==0.147.0 zeroconf==0.147.0
puremagic==1.30 puremagic==1.30
ruamel.yaml==0.18.14 # dashboard_import ruamel.yaml==0.18.14 # dashboard_import