mirror of
https://github.com/esphome/esphome.git
synced 2026-09-18 18:48:39 +00:00
Merge remote-tracking branch 'upstream/eliminate-trigger-trampolines' into integration
This commit is contained in:
+10
-22
@@ -671,8 +671,7 @@ async def build_callback_automation(
|
||||
callback_method: str,
|
||||
args: TemplateArgsType,
|
||||
config: ConfigType,
|
||||
forwarder: MockObjClass | None = None,
|
||||
forwarder_extra_args: list | None = None,
|
||||
forwarder: MockObj | MockObjClass | None = None,
|
||||
) -> None:
|
||||
"""Build an Automation and register it as a callback on the parent.
|
||||
|
||||
@@ -680,7 +679,9 @@ async def build_callback_automation(
|
||||
automation's trigger() directly as a callback on the parent component.
|
||||
|
||||
Uses template forwarder structs so the compiler deduplicates the operator()
|
||||
body across all call sites with the same signature.
|
||||
body across all call sites with the same signature. The forwarder must be
|
||||
pointer-sized (single Automation* field) to fit inline in Callback::ctx_
|
||||
and avoid heap allocation.
|
||||
|
||||
:param parent: The component object (e.g., button, sensor).
|
||||
:param callback_method: Name of the callback method (e.g., "add_on_press_callback").
|
||||
@@ -688,11 +689,7 @@ async def build_callback_automation(
|
||||
:param config: The automation config dict.
|
||||
:param forwarder: Optional forwarder type to use instead of the default
|
||||
TriggerForwarder<Ts...>. Pass any struct type whose aggregate init takes
|
||||
an Automation pointer as the first field (e.g., TriggerOnTrueForwarder,
|
||||
or a custom component-defined forwarder).
|
||||
:param forwarder_extra_args: Optional list of extra MockObj args to pass to the
|
||||
forwarder after the automation pointer in aggregate init. For example,
|
||||
a lock forwarder needs the lock entity pointer: [lock_var].
|
||||
a single Automation pointer (e.g., TriggerOnTrueForwarder).
|
||||
"""
|
||||
arg_types = [arg[0] for arg in args]
|
||||
templ = cg.TemplateArguments(*arg_types)
|
||||
@@ -703,17 +700,8 @@ async def build_callback_automation(
|
||||
# one operator() per forwarder type; different automation pointers are just
|
||||
# data in the struct.
|
||||
if forwarder is None:
|
||||
forwarder = (
|
||||
TriggerForwarder.template(templ)
|
||||
if arg_types
|
||||
else TriggerForwarder.template()
|
||||
)
|
||||
init_args = str(obj)
|
||||
if forwarder_extra_args:
|
||||
extra = ", ".join(str(a) for a in forwarder_extra_args)
|
||||
init_args = f"{init_args}, {extra}"
|
||||
cg.add(
|
||||
getattr(parent, callback_method)(
|
||||
cg.RawExpression(f"{forwarder}{{{init_args}}}")
|
||||
)
|
||||
)
|
||||
forwarder = TriggerForwarder.template(templ)
|
||||
# RawExpression for aggregate init — both forwarder and obj are codegen
|
||||
# MockObjs (not user input), and there's no Expression type for positional
|
||||
# aggregate initialization (StructInitializer uses named fields).
|
||||
cg.add(getattr(parent, callback_method)(cg.RawExpression(f"{forwarder}{{{obj}}}")))
|
||||
|
||||
@@ -92,8 +92,9 @@ def event_schema(
|
||||
@setup_entity("event")
|
||||
async def setup_event_core_(var, config, *, event_types: list[str]):
|
||||
for conf in config.get(CONF_ON_EVENT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.StringRef, "event_type")], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_event_callback", [(cg.StringRef, "event_type")], conf
|
||||
)
|
||||
|
||||
cg.add(var.set_event_types(event_types))
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ LockLockTrigger = lock_ns.class_("LockLockTrigger", automation.Trigger.template(
|
||||
LockUnlockTrigger = lock_ns.class_("LockUnlockTrigger", automation.Trigger.template())
|
||||
|
||||
LockState = lock_ns.enum("LockState")
|
||||
LockStateForwarder = lock_ns.class_("LockStateForwarder")
|
||||
|
||||
LOCK_STATES = {
|
||||
"LOCKED": LockState.LOCK_STATE_LOCKED,
|
||||
@@ -95,23 +94,11 @@ def lock_schema(
|
||||
@setup_entity("lock")
|
||||
async def _setup_lock_core(var, config):
|
||||
for conf in config.get(CONF_ON_LOCK, []):
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_on_state_callback",
|
||||
[],
|
||||
conf,
|
||||
forwarder=LockStateForwarder.template(LockState.LOCK_STATE_LOCKED),
|
||||
forwarder_extra_args=[var],
|
||||
)
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_UNLOCK, []):
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_on_state_callback",
|
||||
[],
|
||||
conf,
|
||||
forwarder=LockStateForwarder.template(LockState.LOCK_STATE_UNLOCKED),
|
||||
forwarder_extra_args=[var],
|
||||
)
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
if mqtt_id := config.get(CONF_MQTT_ID):
|
||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||
|
||||
@@ -66,14 +66,4 @@ template<LockState State> class LockStateTrigger : public Trigger<> {
|
||||
using LockLockTrigger = LockStateTrigger<LockState::LOCK_STATE_LOCKED>;
|
||||
using LockUnlockTrigger = LockStateTrigger<LockState::LOCK_STATE_UNLOCKED>;
|
||||
|
||||
/// Forwarder that triggers an Automation<> when a Lock reaches a specific state.
|
||||
template<LockState State> struct LockStateForwarder {
|
||||
Automation<> *automation;
|
||||
Lock *lock;
|
||||
void operator()() const {
|
||||
if (this->lock->state == State)
|
||||
this->automation->trigger();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::lock
|
||||
|
||||
@@ -470,6 +470,7 @@ template<typename... Ts> class ActionList {
|
||||
|
||||
template<typename... Ts> class Automation {
|
||||
public:
|
||||
/// Default constructor for use with TriggerForwarder (no Trigger object needed).
|
||||
Automation() = default;
|
||||
explicit Automation(Trigger<Ts...> *trigger) { trigger->set_automation_parent(this); }
|
||||
|
||||
@@ -493,12 +494,14 @@ template<typename... Ts> class Automation {
|
||||
|
||||
/// Callback forwarder that triggers an Automation directly.
|
||||
/// One operator() instantiation per Automation<Ts...> signature, shared across all call sites.
|
||||
/// Must stay pointer-sized to fit inline in Callback::ctx_ without heap allocation.
|
||||
template<typename... Ts> struct TriggerForwarder {
|
||||
Automation<Ts...> *automation;
|
||||
void operator()(Ts... args) const { this->automation->trigger(args...); }
|
||||
void operator()(const Ts &...args) const { this->automation->trigger(args...); }
|
||||
};
|
||||
|
||||
/// Callback forwarder that triggers an Automation<> only when the bool arg is true.
|
||||
/// Must stay pointer-sized to fit inline in Callback::ctx_ without heap allocation.
|
||||
struct TriggerOnTrueForwarder {
|
||||
Automation<> *automation;
|
||||
void operator()(bool state) const {
|
||||
@@ -508,6 +511,7 @@ struct TriggerOnTrueForwarder {
|
||||
};
|
||||
|
||||
/// Callback forwarder that triggers an Automation<> only when the bool arg is false.
|
||||
/// Must stay pointer-sized to fit inline in Callback::ctx_ without heap allocation.
|
||||
struct TriggerOnFalseForwarder {
|
||||
Automation<> *automation;
|
||||
void operator()(bool state) const {
|
||||
@@ -516,4 +520,13 @@ struct TriggerOnFalseForwarder {
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure forwarders fit in Callback::ctx_ (pointer-sized inline storage).
|
||||
// If these fail, the forwarder would heap-allocate in Callback::create().
|
||||
static_assert(sizeof(TriggerForwarder<>) <= sizeof(void *));
|
||||
static_assert(sizeof(TriggerOnTrueForwarder) <= sizeof(void *));
|
||||
static_assert(sizeof(TriggerOnFalseForwarder) <= sizeof(void *));
|
||||
static_assert(std::is_trivially_copyable_v<TriggerForwarder<>>);
|
||||
static_assert(std::is_trivially_copyable_v<TriggerOnTrueForwarder>);
|
||||
static_assert(std::is_trivially_copyable_v<TriggerOnFalseForwarder>);
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -187,7 +187,6 @@ def _build_forwarder(
|
||||
automation_name: str,
|
||||
args: list[tuple[str, str]],
|
||||
forwarder: MockObj | None = None,
|
||||
extra_args: list[str] | None = None,
|
||||
) -> str:
|
||||
"""Build a trigger forwarder expression the same way build_callback_automation does.
|
||||
|
||||
@@ -202,10 +201,7 @@ def _build_forwarder(
|
||||
cg.TemplateArguments(*arg_types) if arg_types else cg.TemplateArguments()
|
||||
)
|
||||
forwarder = TriggerForwarder.template(templ)
|
||||
init_args = str(obj)
|
||||
if extra_args:
|
||||
init_args += ", " + ", ".join(extra_args)
|
||||
return f"{forwarder}{{{init_args}}}"
|
||||
return f"{forwarder}{{{obj}}}"
|
||||
|
||||
|
||||
def test_trigger_forwarder_no_args() -> None:
|
||||
@@ -253,10 +249,8 @@ def test_trigger_forwarder_string_arg() -> None:
|
||||
assert result == "TriggerForwarder<std::string>{auto_1}"
|
||||
|
||||
|
||||
def test_trigger_forwarder_custom_with_extra_args() -> None:
|
||||
"""Lock on_lock: custom forwarder with extra args for entity pointer."""
|
||||
lock_forwarder = MockObj("LockStateForwarder<LOCK_STATE_LOCKED>", "")
|
||||
result = _build_forwarder(
|
||||
"auto_1", [], forwarder=lock_forwarder, extra_args=["lock_var"]
|
||||
)
|
||||
assert result == "LockStateForwarder<LOCK_STATE_LOCKED>{auto_1, lock_var}"
|
||||
def test_trigger_forwarder_custom_type() -> None:
|
||||
"""Custom forwarder type passed directly."""
|
||||
custom = MockObj("MyForwarder", "")
|
||||
result = _build_forwarder("auto_1", [], forwarder=custom)
|
||||
assert result == "MyForwarder{auto_1}"
|
||||
|
||||
Reference in New Issue
Block a user