mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 17:14:46 +00:00
Compare commits
4 Commits
dev
...
lvgl-lazy-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2159c53c9f | ||
|
|
c062c1ddbd | ||
|
|
fabc1b584f | ||
|
|
3cee06e930 |
@@ -1,10 +1,36 @@
|
||||
from collections.abc import Callable
|
||||
import re
|
||||
from typing import TypeVar
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.const import CONF_ARGS, CONF_FORMAT
|
||||
|
||||
CONF_IF_NAN = "if_nan"
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def lazy_once(build: Callable[[], T]) -> Callable[[], T]:
|
||||
"""Return a no-arg callable that runs ``build`` at most once and caches it.
|
||||
|
||||
Used to defer voluptuous schema construction until first validation. Many
|
||||
of the lvgl schemas would otherwise be built at module-import time even for
|
||||
YAMLs that never reach them.
|
||||
|
||||
Not thread-safe — concurrent first-callers would each run ``build``. esphome
|
||||
config validation is single-threaded so this is fine in practice. If
|
||||
``build`` raises, the cache stays empty and the next call retries; there is
|
||||
no negative-cache.
|
||||
"""
|
||||
cached: list[T] = []
|
||||
|
||||
def get() -> T:
|
||||
if not cached:
|
||||
cached.append(build())
|
||||
return cached[0]
|
||||
|
||||
return get
|
||||
|
||||
|
||||
# noqa
|
||||
f_regex = re.compile(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.automation import Trigger, validate_automation
|
||||
@@ -40,7 +41,7 @@ from .defines import (
|
||||
get_remapped_uses,
|
||||
is_press_event,
|
||||
)
|
||||
from .helpers import CONF_IF_NAN, validate_printf
|
||||
from .helpers import CONF_IF_NAN, lazy_once, validate_printf
|
||||
from .layout import (
|
||||
FLEX_OBJ_SCHEMA,
|
||||
GRID_CELL_SCHEMA,
|
||||
@@ -534,7 +535,7 @@ def strip_defaults(schema: cv.Schema):
|
||||
return cv.Schema({cv.Optional(k): v for k, v in schema.schema.items()})
|
||||
|
||||
|
||||
def container_schema(widget_type: WidgetType, extras=None):
|
||||
def container_schema(widget_type: WidgetType, extras=None) -> Callable[[Any], Any]:
|
||||
"""
|
||||
Create a schema for a container widget of a given type. All obj properties are available, plus
|
||||
the extras passed in, plus any defined for the specific widget being specified.
|
||||
@@ -542,18 +543,25 @@ 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 = schema.extend(widget_type.schema)
|
||||
# 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.
|
||||
def build() -> cv.Schema:
|
||||
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
|
||||
return built.extend(widget_type.schema)
|
||||
|
||||
def validator(value):
|
||||
get_schema = lazy_once(build)
|
||||
|
||||
def validator(value: Any) -> Any:
|
||||
value = value or {}
|
||||
return append_layout_schema(schema, value)(value)
|
||||
return append_layout_schema(get_schema(), value)(value)
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from collections.abc import Callable
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
from esphome import codegen as cg, config_validation as cv
|
||||
from esphome.automation import register_action
|
||||
@@ -48,6 +50,7 @@ from ..defines import (
|
||||
join_enums,
|
||||
literal,
|
||||
)
|
||||
from ..helpers import lazy_once
|
||||
from ..lv_validation import lv_int
|
||||
from ..lvcode import (
|
||||
LvConditional,
|
||||
@@ -73,6 +76,34 @@ from ..types import (
|
||||
EVENT_LAMB = "event_lamb__"
|
||||
|
||||
|
||||
def _lazy_update_schema(widget_type: "WidgetType") -> Callable[[Any], Any]:
|
||||
"""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.
|
||||
|
||||
The ``from ..schemas import base_update_schema`` import stays inside the
|
||||
closure because ``schemas`` imports ``WidgetType`` from this module — top-
|
||||
level would deadlock the import.
|
||||
"""
|
||||
|
||||
def build() -> Schema:
|
||||
from ..schemas import base_update_schema
|
||||
|
||||
return base_update_schema(widget_type, widget_type.parts).extend(
|
||||
widget_type.modify_schema
|
||||
)
|
||||
|
||||
get_schema = lazy_once(build)
|
||||
|
||||
def validator(value: Any) -> Any:
|
||||
return get_schema()(value)
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
class WidgetType:
|
||||
"""
|
||||
Describes a type of Widget, e.g. "bar" or "line"
|
||||
@@ -113,18 +144,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