From 845d91d8c15550b9a73ff4f9b8c9c8236a213e6b Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 26 Jul 2026 12:39:01 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(core):=20Evita=20doble=20?= =?UTF-8?q?clonado=20de=20InRegion=20en=20Child?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Los prototipos registrados con `InRegion` se clonaban dos veces por petición: una al ensamblar la región (`ComponentGlobal::as_child`) y otra al renderizar (`Child::render`). Ahora sólo se clonan al renderizar. --- src/core/component/children.rs | 13 ++++++-- src/core/theme/regions.rs | 60 +++++++++++++--------------------- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/src/core/component/children.rs b/src/core/component/children.rs index b76a6258..519c2f2f 100644 --- a/src/core/component/children.rs +++ b/src/core/component/children.rs @@ -32,6 +32,13 @@ impl Child { Child(Some(Arc::new(component))) } + // Envuelve un `Arc` ya construido, sin clonar el componente. La usa `core::theme::regions` para + // registrar los prototipos de `InRegion` sin clonar su estado hasta que `render()` obtenga la + // copia propia que necesita mutar. + pub(crate) fn from_arc(component: Arc) -> Self { + Child(Some(component)) + } + // **< Child BUILDER >************************************************************************** /// Establece un componente nuevo, o lo vacía. @@ -73,7 +80,7 @@ impl Child { } } -impl From> for Child { +impl From> for Child { /// Convierte un [`Embed`] en un [`Child`], consumiendo el componente tipado. /// /// Útil cuando se tiene un [`Embed`] para añadir a una lista [`Children`]: @@ -93,7 +100,7 @@ impl From> for Child { } } -impl From for Child { +impl From for Child { /// Convierte cualquier componente en un [`Child`], equivalente a [`Child::with()`]. #[inline] fn from(component: T) -> Self { @@ -101,7 +108,7 @@ impl From for Child { } } -impl From for ChildOp { +impl From for ChildOp { /// Convierte un componente en [`ChildOp::Add`], permitiendo pasar componentes directamente a /// métodos como [`Children::with_child`] sin envolverlos explícitamente. #[inline] diff --git a/src/core/theme/regions.rs b/src/core/theme/regions.rs index f6c00aed..515337f3 100644 --- a/src/core/theme/regions.rs +++ b/src/core/theme/regions.rs @@ -7,26 +7,13 @@ use parking_lot::RwLock; use std::collections::HashMap; use std::sync::{Arc, LazyLock}; -// Permite almacenar un componente como prototipo en regiones globales. +// Mapea cada nombre de región con su lista de prototipos de componentes. // -// Se implementa automáticamente para todo tipo que implemente [`Component`] y [`Clone`]. En cada -// llamada a [`as_child`](Self::as_child) produce un clon fresco del estado original, de modo que -// cada página renderiza el componente desde su estado inicial sin acumular mutaciones de peticiones -// anteriores. -trait ComponentGlobal: Send + Sync { - // Devuelve un nuevo [`Child`] con una copia independiente del componente original. - fn as_child(&self) -> Child; -} - -impl ComponentGlobal for T { - #[inline] - fn as_child(&self) -> Child { - Child::with(self.clone()) - } -} - -// Mapa de nombre de región a lista de prototipos de componentes. -type RegionComponents = HashMap>>; +// Se comparten como `Arc`. El prototipo no se clona al registrarse ni al ensamblar +// la región (sólo se clona el `Arc`, barato). En cambio, sí se realiza un clonado del componente en +// `Child::render()`, cuando cada petición necesita su propia copia mutable para pasar por `setup()` +// desde un estado inicial limpio. +type RegionComponents = HashMap>>; // Regiones globales con prototipos asociados a un tema específico. static THEME_REGIONS: LazyLock>> = @@ -64,23 +51,22 @@ impl ChildrenInRegions { /// /// Se recogen desde tres fuentes disponibles, en el siguiente orden: /// - /// 1. Prototipos globales comunes, disponibles en cualquier tema. Se clonan en cada petición + /// 1. Prototipos globales comunes, disponibles en cualquier tema. Se comparten como `Arc` (sin + /// clonar el componente); `Child::render()` obtiene su propia copia mutable más adelante, /// para que `setup()` parta siempre de un estado inicial limpio. /// 2. Componentes propios de la página, registrados para esta petición concreta. Se mueven en /// lugar de clonarse, ya que son de un único uso. - /// 3. Prototipos del tema activo, exclusivos del tema en curso. También se clonan para asegurar - /// que llegan a `setup()` con el mismo estado inicial. + /// 3. Prototipos del tema activo, exclusivos del tema en curso. Se comparten igual que los + /// comunes. pub fn assemble_region(&mut self, theme: ThemeRef, region: RegionRef) -> Children { - let region_name = region.name(); - let common = COMMON_REGIONS.read(); - let themed = THEME_REGIONS.read(); - let mut result = Children::new(); + let region_name = region.name(); + // 1. Prototipos globales comunes. - if let Some(protos) = common.get(region_name) { - for proto in protos { - result.add(proto.as_child()); + if let Some(global_protos) = COMMON_REGIONS.read().get(region_name) { + for proto in global_protos { + result.add(Child::from_arc(Arc::clone(proto))); } } // 2. Componentes propios de la página: se mueven, no se clonan. @@ -90,11 +76,11 @@ impl ChildrenInRegions { } } // 3. Prototipos del tema activo. - if let Some(theme_map) = themed.get(&theme.type_id()) { - if let Some(protos) = theme_map.get(region_name) { - for proto in protos { - result.add(proto.as_child()); - } + if let Some(theme_region) = THEME_REGIONS.read().get(&theme.type_id()) + && let Some(theme_protos) = theme_region.get(region_name) + { + for proto in theme_protos { + result.add(Child::from_arc(Arc::clone(proto))); } } @@ -168,8 +154,8 @@ impl InRegion { /// html! { "Aviso legal" } /// })); /// ``` - pub fn add(&self, component: impl Component + Clone + 'static) -> &Self { - let proto: Arc = Arc::new(component); + pub fn add(&self, component: impl Component) -> &Self { + let proto: Arc = Arc::new(component); match self { InRegion::Content => Self::add_to_common(&CoreRegion::Content, proto), InRegion::Global(region) => Self::add_to_common(*region, proto), @@ -187,7 +173,7 @@ impl InRegion { } #[inline] - fn add_to_common(region: RegionRef, proto: Arc) { + fn add_to_common(region: RegionRef, proto: Arc) { COMMON_REGIONS .write() .entry(region.name().to_owned())