mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Reserve memory for component and platform vectors (#9042)
This commit is contained in:
		| @@ -235,6 +235,7 @@ async def register_alarm_control_panel(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_alarm_control_panel(var)) | ||||
|     CORE.register_platform_component("alarm_control_panel", var) | ||||
|     await setup_alarm_control_panel_core_(var, config) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -554,6 +554,7 @@ async def register_binary_sensor(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_binary_sensor(var)) | ||||
|     CORE.register_platform_component("binary_sensor", var) | ||||
|     await setup_binary_sensor_core_(var, config) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -108,6 +108,7 @@ async def register_button(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_button(var)) | ||||
|     CORE.register_platform_component("button", var) | ||||
|     await setup_button_core_(var, config) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -443,6 +443,7 @@ async def register_climate(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_climate(var)) | ||||
|     CORE.register_platform_component("climate", var) | ||||
|     await setup_climate_core_(var, config) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -189,6 +189,7 @@ async def register_cover(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_cover(var)) | ||||
|     CORE.register_platform_component("cover", var) | ||||
|     await setup_cover_core_(var, config) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -158,7 +158,9 @@ async def setup_datetime_core_(var, config): | ||||
| async def register_datetime(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(getattr(cg.App, f"register_{config[CONF_TYPE].lower()}")(var)) | ||||
|     entity_type = config[CONF_TYPE].lower() | ||||
|     cg.add(getattr(cg.App, f"register_{entity_type}")(var)) | ||||
|     CORE.register_platform_component(entity_type, var) | ||||
|     await setup_datetime_core_(var, config) | ||||
|     cg.add_define(f"USE_DATETIME_{config[CONF_TYPE]}") | ||||
|  | ||||
|   | ||||
| @@ -113,6 +113,7 @@ async def register_event(var, config, *, event_types: list[str]): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_event(var)) | ||||
|     CORE.register_platform_component("event", var) | ||||
|     await setup_event_core_(var, config, event_types=event_types) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -296,6 +296,7 @@ async def register_fan(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_fan(var)) | ||||
|     CORE.register_platform_component("fan", var) | ||||
|     await setup_fan_core_(var, config) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -37,7 +37,7 @@ from esphome.const import ( | ||||
|     CONF_WEB_SERVER, | ||||
|     CONF_WHITE, | ||||
| ) | ||||
| from esphome.core import coroutine_with_priority | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| from esphome.cpp_generator import MockObjClass | ||||
| from esphome.cpp_helpers import setup_entity | ||||
|  | ||||
| @@ -270,6 +270,7 @@ async def setup_light_core_(light_var, output_var, config): | ||||
| async def register_light(output_var, config): | ||||
|     light_var = cg.new_Pvariable(config[CONF_ID], output_var) | ||||
|     cg.add(cg.App.register_light(light_var)) | ||||
|     CORE.register_platform_component("light", light_var) | ||||
|     await cg.register_component(light_var, config) | ||||
|     await setup_light_core_(light_var, output_var, config) | ||||
|  | ||||
|   | ||||
| @@ -115,6 +115,7 @@ async def register_lock(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_lock(var)) | ||||
|     CORE.register_platform_component("lock", var) | ||||
|     await _setup_lock_core(var, config) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -103,6 +103,7 @@ async def register_media_player(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_media_player(var)) | ||||
|     CORE.register_platform_component("media_player", var) | ||||
|     await setup_media_player_core_(var, config) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -277,6 +277,7 @@ async def register_number( | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_number(var)) | ||||
|     CORE.register_platform_component("number", var) | ||||
|     await setup_number_core_( | ||||
|         var, config, min_value=min_value, max_value=max_value, step=step | ||||
|     ) | ||||
|   | ||||
| @@ -111,6 +111,7 @@ async def register_select(var, config, *, options: list[str]): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_select(var)) | ||||
|     CORE.register_platform_component("select", var) | ||||
|     await setup_select_core_(var, config, options=options) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -167,7 +167,6 @@ DEVICE_CLASSES = [ | ||||
| ] | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| sensor_ns = cg.esphome_ns.namespace("sensor") | ||||
| StateClasses = sensor_ns.enum("StateClass") | ||||
| STATE_CLASSES = { | ||||
| @@ -840,6 +839,7 @@ async def register_sensor(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_sensor(var)) | ||||
|     CORE.register_platform_component("sensor", var) | ||||
|     await setup_sensor_core_(var, config) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -159,6 +159,7 @@ async def register_switch(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_switch(var)) | ||||
|     CORE.register_platform_component("switch", var) | ||||
|     await setup_switch_core_(var, config) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -126,6 +126,7 @@ async def register_text( | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_text(var)) | ||||
|     CORE.register_platform_component("text", var) | ||||
|     await setup_text_core_( | ||||
|         var, config, min_length=min_length, max_length=max_length, pattern=pattern | ||||
|     ) | ||||
|   | ||||
| @@ -215,6 +215,7 @@ async def register_text_sensor(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_text_sensor(var)) | ||||
|     CORE.register_platform_component("text_sensor", var) | ||||
|     await setup_text_sensor_core_(var, config) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -111,6 +111,7 @@ async def register_update(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_update(var)) | ||||
|     CORE.register_platform_component("update", var) | ||||
|     await setup_update_core_(var, config) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -163,6 +163,7 @@ async def register_valve(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_valve(var)) | ||||
|     CORE.register_platform_component("valve", var) | ||||
|     await _setup_valve_core(var, config) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| from collections import defaultdict | ||||
| import logging | ||||
| import math | ||||
| import os | ||||
| @@ -516,6 +517,9 @@ class EsphomeCore: | ||||
|         self.loaded_platforms: set[str] = set() | ||||
|         # A set of component IDs to track what Component subclasses are declared | ||||
|         self.component_ids = set() | ||||
|         # Dict to track platform entity counts for pre-allocation | ||||
|         # Key: platform name (e.g. "sensor", "binary_sensor"), Value: count | ||||
|         self.platform_counts: defaultdict[str, int] = defaultdict(int) | ||||
|         # Whether ESPHome was started in verbose mode | ||||
|         self.verbose = False | ||||
|         # Whether ESPHome was started in quiet mode | ||||
| @@ -545,6 +549,7 @@ class EsphomeCore: | ||||
|         self.platformio_options = {} | ||||
|         self.loaded_integrations = set() | ||||
|         self.component_ids = set() | ||||
|         self.platform_counts = defaultdict(int) | ||||
|         PIN_SCHEMA_REGISTRY.reset() | ||||
|  | ||||
|     @property | ||||
| @@ -669,16 +674,17 @@ class EsphomeCore: | ||||
|     def using_esp_idf(self): | ||||
|         return self.target_framework == "esp-idf" | ||||
|  | ||||
|     def add_job(self, func, *args, **kwargs): | ||||
|     def add_job(self, func, *args, **kwargs) -> None: | ||||
|         self.event_loop.add_job(func, *args, **kwargs) | ||||
|  | ||||
|     def flush_tasks(self): | ||||
|     def flush_tasks(self) -> None: | ||||
|         try: | ||||
|             self.event_loop.flush_tasks() | ||||
|         except RuntimeError as e: | ||||
|             raise EsphomeError(str(e)) from e | ||||
|  | ||||
|     def add(self, expression): | ||||
|     def add(self, expression, prepend=False) -> "Statement": | ||||
|         """Add an expression or statement to the main setup() block.""" | ||||
|         from esphome.cpp_generator import Expression, Statement, statement | ||||
|  | ||||
|         if isinstance(expression, Expression): | ||||
| @@ -688,11 +694,14 @@ class EsphomeCore: | ||||
|                 f"Add '{expression}' must be expression or statement, not {type(expression)}" | ||||
|             ) | ||||
|  | ||||
|         self.main_statements.append(expression) | ||||
|         if prepend: | ||||
|             self.main_statements.insert(0, expression) | ||||
|         else: | ||||
|             self.main_statements.append(expression) | ||||
|         _LOGGER.debug("Adding: %s", expression) | ||||
|         return expression | ||||
|  | ||||
|     def add_global(self, expression, prepend=False): | ||||
|     def add_global(self, expression, prepend=False) -> "Statement": | ||||
|         from esphome.cpp_generator import Expression, Statement, statement | ||||
|  | ||||
|         if isinstance(expression, Expression): | ||||
| @@ -822,6 +831,14 @@ class EsphomeCore: | ||||
|     def has_id(self, id): | ||||
|         return id in self.variables | ||||
|  | ||||
|     def register_platform_component(self, platform_name: str, var) -> None: | ||||
|         """Register a component for a platform and track its count. | ||||
|  | ||||
|         :param platform_name: The name of the platform (e.g., 'sensor', 'binary_sensor') | ||||
|         :param var: The variable (component) being registered (currently unused but kept for future use) | ||||
|         """ | ||||
|         self.platform_counts[platform_name] += 1 | ||||
|  | ||||
|     @property | ||||
|     def cpp_main_section(self): | ||||
|         from esphome.cpp_generator import statement | ||||
|   | ||||
| @@ -198,6 +198,73 @@ class Application { | ||||
|   void register_update(update::UpdateEntity *update) { this->updates_.push_back(update); } | ||||
| #endif | ||||
|  | ||||
|   /// Reserve space for components to avoid memory fragmentation | ||||
|   void reserve_components(size_t count) { this->components_.reserve(count); } | ||||
|  | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   void reserve_binary_sensor(size_t count) { this->binary_sensors_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   void reserve_switch(size_t count) { this->switches_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   void reserve_button(size_t count) { this->buttons_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   void reserve_sensor(size_t count) { this->sensors_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   void reserve_text_sensor(size_t count) { this->text_sensors_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   void reserve_fan(size_t count) { this->fans_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   void reserve_cover(size_t count) { this->covers_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   void reserve_climate(size_t count) { this->climates_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   void reserve_light(size_t count) { this->lights_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
|   void reserve_number(size_t count) { this->numbers_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
|   void reserve_date(size_t count) { this->dates_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   void reserve_time(size_t count) { this->times_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   void reserve_datetime(size_t count) { this->datetimes_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
|   void reserve_select(size_t count) { this->selects_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   void reserve_text(size_t count) { this->texts_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_LOCK | ||||
|   void reserve_lock(size_t count) { this->locks_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   void reserve_valve(size_t count) { this->valves_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   void reserve_media_player(size_t count) { this->media_players_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|   void reserve_alarm_control_panel(size_t count) { this->alarm_control_panels_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
|   void reserve_event(size_t count) { this->events_.reserve(count); } | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
|   void reserve_update(size_t count) { this->updates_.reserve(count); } | ||||
| #endif | ||||
|  | ||||
|   /// Register the component in this Application instance. | ||||
|   template<class C> C *register_component(C *c) { | ||||
|     static_assert(std::is_base_of<Component, C>::value, "Only Component subclasses can be registered"); | ||||
|   | ||||
| @@ -329,6 +329,12 @@ async def _add_automations(config): | ||||
|         await automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|  | ||||
| @coroutine_with_priority(-100.0) | ||||
| async def _add_platform_reserves() -> None: | ||||
|     for platform_name, count in sorted(CORE.platform_counts.items()): | ||||
|         cg.add(cg.RawStatement(f"App.reserve_{platform_name}({count});"), prepend=True) | ||||
|  | ||||
|  | ||||
| @coroutine_with_priority(100.0) | ||||
| async def to_code(config): | ||||
|     cg.add_global(cg.global_ns.namespace("esphome").using) | ||||
| @@ -347,6 +353,12 @@ async def to_code(config): | ||||
|             config[CONF_NAME_ADD_MAC_SUFFIX], | ||||
|         ) | ||||
|     ) | ||||
|     # Reserve space for components to avoid reallocation during registration | ||||
|     cg.add( | ||||
|         cg.RawStatement(f"App.reserve_components({len(CORE.component_ids)});"), | ||||
|     ) | ||||
|  | ||||
|     CORE.add_job(_add_platform_reserves) | ||||
|  | ||||
|     CORE.add_job(_add_automations, config) | ||||
|  | ||||
|   | ||||
| @@ -579,13 +579,13 @@ def new_Pvariable(id_: ID, *args: SafeExpType) -> Pvariable: | ||||
|     return Pvariable(id_, rhs) | ||||
|  | ||||
|  | ||||
| def add(expression: Expression | Statement): | ||||
| def add(expression: Expression | Statement, prepend: bool = False): | ||||
|     """Add an expression to the codegen section. | ||||
|  | ||||
|     After this is called, the given given expression will | ||||
|     show up in the setup() function after this has been called. | ||||
|     """ | ||||
|     CORE.add(expression) | ||||
|     CORE.add(expression, prepend) | ||||
|  | ||||
|  | ||||
| def add_global(expression: SafeExpType | Statement, prepend: bool = False): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user