Replace new-and-leak deprecated setters with plain owned vector members
on ClimateTraits. Copies copy the vector (same cost as before this PR).
No heap leak, no dangling pointer, no smart pointer overhead.
All compat paths (getters, find, supports) check the owned vector as
fallback when the pointer path is not set.
- Move supported_custom_fan_modes_ and supported_custom_presets_ pointers
and ensure helpers to private section
- Move static EMPTY_VECTOR from inline header getters to a single
file-scope constant in climate_traits.cpp (avoids duplication per TU)
- Add 2026.11.0 removal comments on all compat code paths
Two bugs fixed from Copilot review:
1. Dangling pointer on copy: deprecated setters stored data in an
OwnedCustomModes struct on ClimateTraits. If the traits object was
copied (when NRVO doesn't apply), the copy's pointer dangled.
Fix: deprecated setters now heap-allocate (intentional leak, same
pattern as Climate entity). Pointer survives any copy. Remove the
OwnedCustomModes wrapper entirely.
2. find_custom_fan_mode_ / find_custom_preset_ only searched the
Climate-owned vectors, breaking external components using the
deprecated traits setters. Fix: fall back to get_traits() when
the entity vector is null.
Climate entities live for the entire program lifetime, so the custom
mode vectors never need to be freed. Use raw pointers (null by default,
allocated on first set_supported_custom_*() call) instead of inline
std::vector members.
This saves 48 bytes of RAM per Climate instance (two empty vectors)
for components that don't use custom modes (bang_bang, pid, etc.),
and avoids any copy overhead in get_traits().
Wrap the deprecated owned vectors in a struct with a no-op copy
constructor. This way ClimateTraits copies (which happen on every
get_traits() call) don't pay the 48-byte cost of copying two empty
vectors. The compat data only matters for the original traits object
where the deprecated setter was called.
The owned vectors add 48 bytes to ClimateTraits copies — this is the
cost of backward compat and will be removed in 2026.11.0.
Add ClimatePublish_WithCustomModes benchmark to show the improvement
for climate devices that use custom fan modes and presets (the case
that previously heap-allocated on every publish).
Keep the old set_supported_custom_fan_modes() and
set_supported_custom_presets() overloads on ClimateTraits as deprecated
compatibility shims. They self-own the data so external components
continue to compile without changes (heap alloc but functional).
Restores get_supported_custom_fan_modes() / get_supported_custom_presets()
to return const vector & (not pointer) for full backward compat.
ClimateTraits contained two std::vector<const char *> members for custom
fan modes and custom presets. Every get_traits() / traits() call
reconstructed these vectors, causing heap allocations on every
publish_state() and control()/perform() call — the hottest paths in the
climate component.
Move the vector storage to the Climate base class and have ClimateTraits
hold const pointers instead. get_traits() wires the pointers
automatically. This eliminates all heap allocation from ClimateTraits
copies, making the struct trivially copyable (floats + bitmasks +
2 pointers).
Additionally, set_custom_fan_mode_() and set_custom_preset_() no longer
need to call get_traits() just to search for a mode — they search the
Climate-owned vectors directly, removing another traits rebuild from the
control/perform path.