A single lambda for both edges was two lambdas before, each with its own
static state, so it becomes one function per edge again. The generated
code picks the delay for the edge, so only that value is evaluated; the
helper that took both delays goes.
The single value form is its own statement, so a lambda delay is one
function. DelayedOnOffFilter only existed for codegen; DelayedOnFilter
and DelayedOffFilter schedule through Filter::delay_output, so all
delay filters share one scheduler callback type.
The generated function picks the delay and schedules the output through
Filter::delay_output, so the filter object drops its two TemplatableFn
and equal filters share one function. A single delay value is used for
both edges.
Filter functions keep the parameters the filter class passes, so they are
emitted directly instead of through _apply_function, which now turns value
parameters into const references. The filter body renders through
_render_call, which replaced _render_values.
delayed_on and delayed_off go back to picking the edge in the generated
code, so a lambda delay is only evaluated on its edge; their inline
helpers go.
A single lambda for both edges was two lambdas before, each with its own
static state, so it becomes one function per edge again. The generated
code picks the delay for the edge, so only that value is evaluated; the
helper that took both delays goes.
Resolved onto the C++ helpers: delayed_on and delayed_off call
delayed_on_output and delayed_off_output, so every delay statement is a
plain string with one placeholder per value.
Resolved onto the free delay_output: cancel_and_pass becomes a free
codegen-only function too, the delay ApplyCalls are module constants,
the delay_output tests move to apply_filter_test.cpp, and the duplicate
pending output test goes.
The private cache goes; _apply_function passes share on to
apply_static_function. ApplyFilterDomain.new shares only when asked,
value lambda functions are named by apply_static_function, statements
become ApplyCall in one place and render with _render_values, and one
helper checks a call's keys against the schema.
A sensor offset lambda and a binary delayed_on_off lambda read an entity
with id x. The round, round_to_multiple_of and calibrate_polynomial cases
go, since no change touches those filters; calibrate_linear exact stays
as a control. Each test reads the log lines once.
delay_output schedules even a 0 ms delay again, so edges within one loop
pass still coalesce. The undelayed edge of delayed_on and delayed_off
calls cancel_and_pass, which drops the pending output and passes the
value on, as the old classes did.
Filter::delay_output with a delay of 0 drops any pending output and
passes the value on at once, which is what delayed_on and delayed_off do
on their undelayed edge, so each is one call with the delay picked by the
value. DelayedOnFilter and DelayedOffFilter only existed for codegen.
A delayed_on_off delay of 0 now passes each value at once instead of one
scheduler pass later.
The single value form is its own statement, so a lambda delay is one
function. DelayedOnOffFilter only existed for codegen; DelayedOnFilter
and DelayedOffFilter schedule through Filter::delay_output, so all
delay filters share one scheduler callback type.
ApplyFilterDomain.build renders the statements over a config, for a
builder that picks them by the shape of its config; prepare is dropped.
The shared functions live in a CORE.data dataclass. Each domain's
APPLY_FILTER sits under its ApplyFilter, and every domain's test checks
the filter has no bytes beyond its base.
Inlined into the filter's function, a value lambda saw the function's x
parameter, so an id named x, such as id(x).state, no longer named that
entity and failed to compile. Each value lambda is now a function
without parameters, called from the filter's function, as it was its
own lambda before. The integration tests cover an id named x in a
sensor offset lambda and a binary sensor delayed_on_off lambda.
The generated function picks the delay and schedules the output through
Filter::delay_output, so the filter object drops its two TemplatableFn
and equal filters share one function. A single delay value is used for
both edges.
ApplyFilter<Fn> in each domain is a filter whose work and constants are
one generated function, so the object is only the Filter base; the
binary sensor one passes the filter to the function so it can schedule
output. ApplyFilterDomain declares a domain's filters: register() builds
the function from ApplyCall statements, with constants baked in and
value lambdas called inline, and new() takes the statements directly.
Identical functions without user code are emitted once, so equal
constant filters share one class. LambdaExpression.body_lines gives a
lambda's statements with their #line marks for a generated function.
Integration tests run offset, multiply, clamp, NTC, calibrate_linear,
calibrate_polynomial, lambda and round filters over finite, NaN and
infinite inputs, and the binary sensor delayed_on_off, delayed_on,
delayed_off and lambda filters over one timed input sequence.
2026-10-28 13:58:08 -10:00
7 changed files with 87 additions and 82 deletions
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.