mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 14:01:01 +00:00
[lvgl] Build container_schema and widget update schemas lazily
Voluptuous schema construction inside container_schema() and the register_action() chain in WidgetType.__init__ dominates cold-validate time for any YAML that imports the lvgl component. cProfile of a cold `import esphome.components.lvgl` shows ~225ms cumulative inside container_schema (9 callers) and ~196ms cumulative inside base_update_schema (30 callers, one per widget type), out of ~400ms total. Defer both: container_schema now returns a validator that builds its underlying schema on first call, and WidgetType registers a lazy validator with register_action so base_update_schema is only evaluated when an `lvgl.<widget>.update` action is actually validated. Measured on M-series Mac, fresh subprocess, 5 cold imports: Before: 389, 385, 389, 384, 380 ms (avg 385ms) After: 229, 217, 218, 215, 215 ms (avg 219ms) A 43% reduction in `import esphome.components.lvgl`. Real `esphome -q config` on tests/components/lvgl/test.host.yaml drops from ~770ms to ~550ms (warm-cache subprocess). Extrapolated to slower hardware (Celeron N5105 dashboard hosts) this is ~1.5s saved per cold LVGL save. Schema output is unchanged: lvgl test configs validate identically and `script/build_language_schema.py` produces a byte-identical lvgl sub-tree before vs after.
This commit is contained in:
@@ -542,18 +542,27 @@ def container_schema(widget_type: WidgetType, extras=None):
|
||||
:param extras: Additional options to be made available, e.g. layout properties for children
|
||||
:return: The schema for this type of widget.
|
||||
"""
|
||||
schema = obj_schema(widget_type).extend(
|
||||
{cv.GenerateID(): cv.declare_id(widget_type.w_type)}
|
||||
)
|
||||
if extras:
|
||||
schema = schema.extend(extras)
|
||||
# Delayed evaluation for recursion
|
||||
# Schema construction is deferred until first validation. obj_schema and the
|
||||
# part_schema -> base_update_schema chain account for ~225ms of cumulative
|
||||
# work at lvgl module import time across ~9 callers; building lazily keeps
|
||||
# `esphome config` fast for YAMLs that never reach this widget type.
|
||||
schema_holder: list = []
|
||||
|
||||
schema = schema.extend(widget_type.schema)
|
||||
def build() -> cv.Schema:
|
||||
if not schema_holder:
|
||||
built = obj_schema(widget_type).extend(
|
||||
{cv.GenerateID(): cv.declare_id(widget_type.w_type)}
|
||||
)
|
||||
if extras:
|
||||
built = built.extend(extras)
|
||||
# Delayed evaluation for recursion
|
||||
built = built.extend(widget_type.schema)
|
||||
schema_holder.append(built)
|
||||
return schema_holder[0]
|
||||
|
||||
def validator(value):
|
||||
value = value or {}
|
||||
return append_layout_schema(schema, value)(value)
|
||||
return append_layout_schema(build(), value)(value)
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
@@ -73,6 +73,30 @@ from ..types import (
|
||||
EVENT_LAMB = "event_lamb__"
|
||||
|
||||
|
||||
def _lazy_update_schema(widget_type: "WidgetType"):
|
||||
"""Defer construction of a widget's update-action schema until first use.
|
||||
|
||||
base_update_schema(...).extend(widget_type.modify_schema) compiles several
|
||||
voluptuous schemas and is invoked once per WidgetType at import time. The
|
||||
caller (register_action) only needs a validator, so wrap the build in a
|
||||
closure that materialises on the first validation and caches the result.
|
||||
"""
|
||||
compiled: list = []
|
||||
|
||||
def validator(value):
|
||||
if not compiled:
|
||||
from ..schemas import base_update_schema
|
||||
|
||||
compiled.append(
|
||||
base_update_schema(widget_type, widget_type.parts).extend(
|
||||
widget_type.modify_schema
|
||||
)
|
||||
)
|
||||
return compiled[0](value)
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
class WidgetType:
|
||||
"""
|
||||
Describes a type of Widget, e.g. "bar" or "line"
|
||||
@@ -113,18 +137,23 @@ class WidgetType:
|
||||
|
||||
# Local import to avoid circular import
|
||||
from ..automation import update_to_code
|
||||
from ..schemas import WIDGET_TYPES, base_update_schema
|
||||
from ..schemas import WIDGET_TYPES
|
||||
|
||||
if not is_mock:
|
||||
if self.name in WIDGET_TYPES:
|
||||
raise EsphomeError(f"Duplicate definition of widget type '{self.name}'")
|
||||
WIDGET_TYPES[self.name] = self
|
||||
|
||||
# Register the update action automatically, adding widget-specific properties
|
||||
# Register the update action automatically, adding widget-specific
|
||||
# properties. The update schema is built lazily on first validation:
|
||||
# base_update_schema involves part_schema / Schema.extend chains that
|
||||
# cost ~7ms per widget at import time (~200ms total across ~25
|
||||
# widgets) and is unused for any YAML that never triggers a
|
||||
# `lvgl.<widget>.update` action.
|
||||
register_action(
|
||||
f"lvgl.{self.name}.update",
|
||||
ObjUpdateAction,
|
||||
base_update_schema(self, self.parts).extend(self.modify_schema),
|
||||
_lazy_update_schema(self),
|
||||
synchronous=True,
|
||||
)(update_to_code)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user