From 24d4da1021244e33ddd385aa3194aeef8de4525b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 May 2026 20:18:28 -0500 Subject: [PATCH] [sensor] Document why TimeoutFilterBase intentionally keeps Component (#16194) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- esphome/components/sensor/filter.h | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 57a2386a7f..b79bfa17d6 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -413,7 +413,31 @@ class ThrottleWithPriorityNanFilter : public Filter { uint32_t min_time_between_inputs_; }; -// Base class for timeout filters - contains common loop logic +// Base class for timeout filters - contains common loop logic. +// +// Why this intentionally inherits Component (and does NOT use the self-keyed +// `App.scheduler.set_timeout(this, ...)` pattern that the other Filter classes +// migrated to): +// +// Timeout filters re-arm on every input, so on devices with many sensors +// using timeout filters (e.g. multi-LD2450 boards) every armed filter would +// require a live SchedulerItem in RAM at the same time. A SchedulerItem is +// substantially larger than the Component bookkeeping bytes carried by this +// class, so paying the Component cost per filter (one-time, BSS) is cheaper +// than paying for a SchedulerItem per filter (live, while armed). #11922 +// is the original symptom and switchover to the loop-based design; #16173 +// attempted to migrate this onto the scheduler and was closed for exactly +// this reason — even if the scheduler pool were unbounded, RAM per armed +// filter would still be dominated by the SchedulerItem itself, not by +// anything we can shrink in the scheduler. +// +// The loop-based design has additional advantages on top of the RAM win: +// `enable_loop()` / `disable_loop()` partitions the cost away when no +// timeout is armed; while armed, work is a single timestamp compare per +// active filter, with no per-input scheduler cancel/insert path. +// +// Don't try to migrate this class onto the self-keyed scheduler. The math +// doesn't work — at scale, this design is the smaller one. class TimeoutFilterBase : public Filter, public Component { public: void loop() override;