From 58d1fef4f1e22768f71118634332670bfe82af3b Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 26 Sep 2026 00:56:30 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20(core):=20Registro=20de=20?= =?UTF-8?q?acciones=20inmutable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-admin/src/action.rs | 20 +- extensions/pagetop-user/src/permission.rs | 5 +- src/auth.rs | 23 +- .../component/after_render_component.rs | 30 +- .../component/before_render_component.rs | 30 +- .../component/transform_markup_component.rs | 41 +-- src/base/action/page/after_render_body.rs | 5 +- src/base/action/page/before_render_body.rs | 5 +- src/core/action.rs | 6 +- src/core/action/all.rs | 268 ++++++++++++---- src/core/action/definition.rs | 93 +++--- src/core/action/list.rs | 25 +- src/core/extension/all.rs | 15 +- src/core/extension/definition.rs | 14 +- tests/action_dispatch.rs | 288 ++++++++++++++++++ 15 files changed, 624 insertions(+), 244 deletions(-) create mode 100644 tests/action_dispatch.rs diff --git a/extensions/pagetop-admin/src/action.rs b/extensions/pagetop-admin/src/action.rs index 019b839a..6bfa7ce5 100644 --- a/extensions/pagetop-admin/src/action.rs +++ b/extensions/pagetop-admin/src/action.rs @@ -117,10 +117,7 @@ impl DeclareAdminSections { } pub(crate) fn dispatch(bag: &mut SectionBag) { - dispatch_actions( - &ActionKey::new(UniqueId::of::(), None, None), - |action: &Self| (action.f)(bag), - ); + dispatch_actions(|action: &Self| (action.f)(bag)); } } @@ -173,10 +170,7 @@ impl DeclareAdminPages { } pub(crate) fn dispatch(bag: &mut PageBag) { - dispatch_actions( - &ActionKey::new(UniqueId::of::(), None, None), - |action: &Self| (action.f)(bag), - ); + dispatch_actions(|action: &Self| (action.f)(bag)); } } @@ -229,10 +223,7 @@ impl DeclareAdminTasks { } pub(crate) fn dispatch(bag: &mut TaskBag) { - dispatch_actions( - &ActionKey::new(UniqueId::of::(), None, None), - |action: &Self| (action.f)(bag), - ); + dispatch_actions(|action: &Self| (action.f)(bag)); } } @@ -283,9 +274,6 @@ impl DeclareAdminActions { } pub(crate) fn dispatch(bag: &mut ActionBag) { - dispatch_actions( - &ActionKey::new(UniqueId::of::(), None, None), - |action: &Self| (action.f)(bag), - ); + dispatch_actions(|action: &Self| (action.f)(bag)); } } diff --git a/extensions/pagetop-user/src/permission.rs b/extensions/pagetop-user/src/permission.rs index c4abddaa..9e6e959f 100644 --- a/extensions/pagetop-user/src/permission.rs +++ b/extensions/pagetop-user/src/permission.rs @@ -58,10 +58,7 @@ impl DeclarePermissions { /// Despacha todas las acciones `DeclarePermissions` registradas construyendo el catálogo. pub(crate) fn dispatch(registry: &mut PermissionRegistry) { - dispatch_actions( - &ActionKey::new(UniqueId::of::(), None, None), - |action: &Self| (action.handler)(registry), - ); + dispatch_actions(|action: &Self| (action.handler)(registry)); } } diff --git a/src/auth.rs b/src/auth.rs index 393c61e0..d12135e8 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -16,11 +16,11 @@ //! //! [`Context`]: crate::core::component::Context -use crate::core::action::{ActionDispatcher, ActionKey, try_dispatch_actions}; +use crate::core::action::{ActionDispatcher, try_dispatch_actions}; use crate::locale::Lc; use crate::response::ErrorPage; use crate::web::HttpRequest; -use crate::{CowStr, UniqueId, Weight}; +use crate::{CowStr, Weight}; // **< CurrentUser >******************************************************************************** @@ -205,17 +205,14 @@ impl CheckPermission { #[inline] pub(crate) fn check(request: &HttpRequest, perm: PermissionRef) -> bool { let mut granted = false; - try_dispatch_actions( - &ActionKey::new(UniqueId::of::(), None, None), - |action: &Self| { - (action.f)(request, perm, &mut granted); - if granted { - std::ops::ControlFlow::Break(()) - } else { - std::ops::ControlFlow::Continue(()) - } - }, - ); + try_dispatch_actions(|action: &Self| { + (action.f)(request, perm, &mut granted); + if granted { + std::ops::ControlFlow::Break(()) + } else { + std::ops::ControlFlow::Continue(()) + } + }); granted } } diff --git a/src/base/action/component/after_render_component.rs b/src/base/action/component/after_render_component.rs index 3a9901fc..ef1b111a 100644 --- a/src/base/action/component/after_render_component.rs +++ b/src/base/action/component/after_render_component.rs @@ -5,19 +5,14 @@ use super::FnActionWithComponent; /// Ejecuta [`FnActionWithComponent`] después de renderizar un componente. pub struct AfterRender { f: FnActionWithComponent, - referer_type_id: Option, - referer_id: Option, + referer: ActionReferer, weight: Weight, } // Filtro para despachar `FnActionWithComponent` después de renderizar un componente `C`. impl ActionDispatcher for AfterRender { - fn referer_type_id(&self) -> Option { - self.referer_type_id - } - - fn referer_id(&self) -> Option { - self.referer_id.clone() + fn referer(&self) -> Option<&ActionReferer> { + Some(&self.referer) } fn weight(&self) -> Weight { @@ -30,8 +25,7 @@ impl AfterRender { pub fn new(f: FnActionWithComponent) -> Self { AfterRender { f, - referer_type_id: Some(UniqueId::of::()), - referer_id: None, + referer: ActionReferer::of::(), weight: 0, } } @@ -39,7 +33,7 @@ impl AfterRender { /// Afina el registro para ejecutar la acción [`FnActionWithComponent`] sólo para el componente /// `C` con identificador `id`. pub fn filter_by_referer_id(mut self, id: impl AsRef) -> Self { - self.referer_id = util::normalize_token(id); + self.referer = self.referer.with_id(id); self } @@ -52,18 +46,6 @@ impl AfterRender { /// Despacha las acciones. #[inline] pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { - // Primero despacha las acciones para el tipo de componente. - dispatch_actions( - &ActionKey::new(UniqueId::of::(), Some(UniqueId::of::()), None), - |action: &Self| (action.f)(component, cx), - ); - - // Y luego despacha las acciones para el tipo de componente con un identificador dado. - if let Some(id) = component.id() { - dispatch_actions( - &ActionKey::new(UniqueId::of::(), Some(UniqueId::of::()), Some(id)), - |action: &Self| (action.f)(component, cx), - ); - } + dispatch_referer(component, |c| c.id(), |action: &Self, c| (action.f)(c, cx)); } } diff --git a/src/base/action/component/before_render_component.rs b/src/base/action/component/before_render_component.rs index 08cd46d4..b57b3236 100644 --- a/src/base/action/component/before_render_component.rs +++ b/src/base/action/component/before_render_component.rs @@ -5,19 +5,14 @@ use super::FnActionWithComponent; /// Ejecuta [`FnActionWithComponent`] antes de renderizar el componente. pub struct BeforeRender { f: FnActionWithComponent, - referer_type_id: Option, - referer_id: Option, + referer: ActionReferer, weight: Weight, } // Filtro para despachar `FnActionWithComponent` antes de renderizar un componente `C`. impl ActionDispatcher for BeforeRender { - fn referer_type_id(&self) -> Option { - self.referer_type_id - } - - fn referer_id(&self) -> Option { - self.referer_id.clone() + fn referer(&self) -> Option<&ActionReferer> { + Some(&self.referer) } fn weight(&self) -> Weight { @@ -30,8 +25,7 @@ impl BeforeRender { pub fn new(f: FnActionWithComponent) -> Self { BeforeRender { f, - referer_type_id: Some(UniqueId::of::()), - referer_id: None, + referer: ActionReferer::of::(), weight: 0, } } @@ -39,7 +33,7 @@ impl BeforeRender { /// Afina el registro para ejecutar la acción [`FnActionWithComponent`] sólo para el componente /// `C` con identificador `id`. pub fn filter_by_referer_id(mut self, id: impl AsRef) -> Self { - self.referer_id = util::normalize_token(id); + self.referer = self.referer.with_id(id); self } @@ -52,18 +46,6 @@ impl BeforeRender { /// Despacha las acciones. #[inline] pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { - // Primero despacha las acciones para el tipo de componente. - dispatch_actions( - &ActionKey::new(UniqueId::of::(), Some(UniqueId::of::()), None), - |action: &Self| (action.f)(component, cx), - ); - - // Y luego despacha las aciones para el tipo de componente con un identificador dado. - if let Some(id) = component.id() { - dispatch_actions( - &ActionKey::new(UniqueId::of::(), Some(UniqueId::of::()), Some(id)), - |action: &Self| (action.f)(component, cx), - ); - } + dispatch_referer(component, |c| c.id(), |action: &Self, c| (action.f)(c, cx)); } } diff --git a/src/base/action/component/transform_markup_component.rs b/src/base/action/component/transform_markup_component.rs index 64a4a507..f79e8adc 100644 --- a/src/base/action/component/transform_markup_component.rs +++ b/src/base/action/component/transform_markup_component.rs @@ -5,19 +5,14 @@ use super::FnActionTransformMarkup; /// Ejecuta [`FnActionTransformMarkup`] para alterar el renderizado de componentes. pub struct TransformMarkup { f: FnActionTransformMarkup, - referer_type_id: Option, - referer_id: Option, + referer: ActionReferer, weight: Weight, } // Filtro para despachar `FnActionTransformMarkup` sobre el renderizado de un componente `C`. impl ActionDispatcher for TransformMarkup { - fn referer_type_id(&self) -> Option { - self.referer_type_id - } - - fn referer_id(&self) -> Option { - self.referer_id.clone() + fn referer(&self) -> Option<&ActionReferer> { + Some(&self.referer) } fn weight(&self) -> Weight { @@ -30,8 +25,7 @@ impl TransformMarkup { pub fn new(f: FnActionTransformMarkup) -> Self { TransformMarkup { f, - referer_type_id: Some(UniqueId::of::()), - referer_id: None, + referer: ActionReferer::of::(), weight: 0, } } @@ -39,7 +33,7 @@ impl TransformMarkup { /// Afina el registro para ejecutar la acción [`FnActionTransformMarkup`] sólo para el /// componente `C` con identificador `id`. pub fn filter_by_referer_id(mut self, id: impl AsRef) -> Self { - self.referer_id = util::normalize_token(id); + self.referer = self.referer.with_id(id); self } @@ -51,29 +45,16 @@ impl TransformMarkup { /// Despacha las acciones encadenando el [`Markup`] entre cada una. #[inline] - pub(crate) fn dispatch(component: &C, cx: &Context, markup: Markup) -> Markup { + pub(crate) fn dispatch(component: &mut C, cx: &Context, markup: Markup) -> Markup { let mut output = markup; - - // Primero despacha las acciones para el tipo de componente. - dispatch_actions( - &ActionKey::new(UniqueId::of::(), Some(UniqueId::of::()), None), - |action: &Self| { + dispatch_referer( + component, + |c| c.id(), + |action: &Self, c| { let taken = std::mem::replace(&mut output, html! {}); - output = (action.f)(component, cx, taken); + output = (action.f)(c, cx, taken); }, ); - - // Y luego despacha las acciones para el tipo de componente con un identificador dado. - if let Some(id) = component.id() { - dispatch_actions( - &ActionKey::new(UniqueId::of::(), Some(UniqueId::of::()), Some(id)), - |action: &Self| { - let taken = std::mem::replace(&mut output, html! {}); - output = (action.f)(component, cx, taken); - }, - ); - } - output } } diff --git a/src/base/action/page/after_render_body.rs b/src/base/action/page/after_render_body.rs index ba1d90ea..522096fe 100644 --- a/src/base/action/page/after_render_body.rs +++ b/src/base/action/page/after_render_body.rs @@ -37,9 +37,6 @@ impl AfterRenderBody { /// Despacha las acciones. #[inline] pub(crate) fn dispatch(page: &mut Page) { - dispatch_actions( - &ActionKey::new(UniqueId::of::(), None, None), - |action: &Self| (action.f)(page), - ); + dispatch_actions(|action: &Self| (action.f)(page)); } } diff --git a/src/base/action/page/before_render_body.rs b/src/base/action/page/before_render_body.rs index 2861ebca..f5c2a1b3 100644 --- a/src/base/action/page/before_render_body.rs +++ b/src/base/action/page/before_render_body.rs @@ -37,9 +37,6 @@ impl BeforeRenderBody { /// Despacha las acciones. #[inline] pub(crate) fn dispatch(page: &mut Page) { - dispatch_actions( - &ActionKey::new(UniqueId::of::(), None, None), - |action: &Self| (action.f)(page), - ); + dispatch_actions(|action: &Self| (action.f)(page)); } } diff --git a/src/core/action.rs b/src/core/action.rs index 3b168e21..375cd199 100644 --- a/src/core/action.rs +++ b/src/core/action.rs @@ -5,14 +5,14 @@ //! aplicación. mod definition; -pub use definition::{ActionBox, ActionDispatcher, ActionKey}; +pub use definition::{ActionBox, ActionDispatcher, ActionReferer}; mod list; use list::ActionsList; mod all; -pub(crate) use all::add_action; -pub use all::{dispatch_actions, try_dispatch_actions}; +pub(crate) use all::publish_actions; +pub use all::{dispatch_actions, dispatch_referer, try_dispatch_actions}; // **< actions! >*********************************************************************************** diff --git a/src/core/action/all.rs b/src/core/action/all.rs index 13ad9716..5b7126e7 100644 --- a/src/core/action/all.rs +++ b/src/core/action/all.rs @@ -1,46 +1,160 @@ -use crate::core::action::{ActionBox, ActionDispatcher, ActionKey, ActionsList}; - -use parking_lot::RwLock; +use crate::UniqueId; +use crate::core::action::{ActionBox, ActionDispatcher, ActionReferer, ActionsList}; use std::collections::HashMap; -use std::sync::LazyLock; +use std::hash::{BuildHasherDefault, Hasher}; +use std::sync::OnceLock; // **< ACCIONES >*********************************************************************************** -static ACTIONS: LazyLock>> = - LazyLock::new(|| RwLock::new(HashMap::new())); +// Registro de acciones. Se construye una sola vez durante el arranque, al registrar las extensiones +// y antes de inicializarlas (`publish_actions()`). A partir de ahí es inmutable, por lo que +// despachar una acción no toma ningún bloqueo ni escribe en memoria compartida, evitando cualquier +// contención por más hilos que atiendan peticiones. +static ACTIONS: OnceLock = OnceLock::new(); -// **< AÑADIR ACCIONES >**************************************************************************** +// Una clave del registro usa `TypeId`, que ya es un *hash* bien distribuido. No requiere SipHash. +type FastMap = HashMap>; -/// Registra una nueva acción en el sistema. -/// -/// Si ya existen acciones con la misma `ActionKey`, la acción se añade a la misma lista. Si no, se -/// crea una nueva lista. -/// -/// Las extensiones llamarán a esta función durante su inicialización para instalar acciones -/// personalizadas que modifiquen el comportamiento del *core* o de otros componentes. -pub(crate) fn add_action(action: ActionBox) { - let key = ActionKey::new( - action.type_id(), - action.referer_type_id(), - action.referer_id(), - ); - let mut actions = ACTIONS.write(); - if let Some(list) = actions.get_mut(&key) { - list.add(action); - } else { - let mut list = ActionsList::new(); - list.add(action); - actions.insert(key, list); +struct Registry(FastMap); + +// Tipo de acción y, si lo hay, tipo del referente (p. ej. el componente). Es `Copy`, así que +// buscarlo no reserva memoria; el identificador del referente se busca aparte, en `ActionEntry`. +#[derive(Clone, Copy, Eq, Hash, PartialEq)] +struct Slot { + action_type_id: UniqueId, + referer_type_id: Option, +} + +// Acciones de un tipo para un tipo de referente: las generales y, aparte, las filtradas por +// identificador. Es de sólo lectura y se obtiene ya construida del registro, sin bloqueos. +#[derive(Default)] +pub(crate) struct ActionEntry { + general: ActionsList, + by_id: HashMap, +} + +impl ActionEntry { + // Acciones que se aplican a cualquier referente del tipo. + pub(crate) fn general(&self) -> &ActionsList { + &self.general + } + + // Indica si hay alguna acción filtrada por identificador. Permite no calcular el identificador + // del referente (que puede reservar memoria) cuando no hay ninguna. + pub(crate) fn has_ids(&self) -> bool { + !self.by_id.is_empty() + } + + // Acciones que sólo se aplican al referente con ese identificador. + pub(crate) fn with_id(&self, id: &str) -> Option<&ActionsList> { + self.by_id.get(id) } } -// **< DESPLEGAR ACCIONES >************************************************************************* +// Busca la entrada de un tipo de acción y de referente. +fn action_entry( + action_type_id: UniqueId, + referer_type_id: Option, +) -> Option<&'static ActionEntry> { + ACTIONS.get()?.0.get(&Slot { + action_type_id, + referer_type_id, + }) +} -/// Despacha y ejecuta las funciones asociadas a una [`ActionKey`]. +// **< REGISTRAR ACCIONES >************************************************************************* + +// Registra todas las acciones de las extensiones y congela el registro. +// +// Las extensiones instalan sus acciones durante el arranque, antes de atender ninguna petición, y +// sólo aquí. `collect` se ejecuta una única vez: si el registro ya está construido (p. ej. por otra +// llamada a `Application::prepare()` en el mismo proceso, como hacen las pruebas) no se vuelve a +// construir ni se duplican las acciones. +pub(crate) fn publish_actions(collect: impl FnOnce() -> Vec) { + ACTIONS.get_or_init(|| { + let mut slots: FastMap = FastMap::default(); + for action in collect() { + let referer = action.referer(); + let slot = Slot { + action_type_id: action.type_id(), + referer_type_id: referer.map(ActionReferer::referer_type_id), + }; + let entry = slots.entry(slot).or_default(); + match referer.and_then(|r| r.id().map(str::to_owned)) { + None => entry.general.add(action), + Some(id) => entry.by_id.entry(id).or_default().add(action), + } + } + Registry(slots) + }); +} + +// Función de *hash* rápida para claves formadas por `TypeId` (multiplicación y rotación, como +// FxHash). No es apta para claves controladas por un atacante; aquí las claves son tipos. +#[derive(Default)] +struct FastHasher(u64); + +impl FastHasher { + const SEED: u64 = 0x51_7c_c1_b7_27_22_0a_95; + + #[inline] + fn add(&mut self, word: u64) { + self.0 = (self.0.rotate_left(5) ^ word).wrapping_mul(Self::SEED); + } +} + +impl Hasher for FastHasher { + #[inline] + fn write(&mut self, bytes: &[u8]) { + let mut chunks = bytes.chunks_exact(8); + for chunk in &mut chunks { + self.add(u64::from_le_bytes(chunk.try_into().unwrap())); + } + let rest = chunks.remainder(); + if !rest.is_empty() { + let mut last = [0u8; 8]; + last[..rest.len()].copy_from_slice(rest); + self.add(u64::from_le_bytes(last)); + } + } + + #[inline] + fn write_u8(&mut self, n: u8) { + self.add(n as u64); + } + + #[inline] + fn write_u64(&mut self, n: u64) { + self.add(n); + } + + #[inline] + fn write_u128(&mut self, n: u128) { + self.add(n as u64); + self.add((n >> 64) as u64); + } + + #[inline] + fn write_isize(&mut self, n: isize) { + self.add(n as u64); + } + + #[inline] + fn finish(&self) -> u64 { + self.0.rotate_left(26) + } +} + +// **< DESPACHAR ACCIONES >************************************************************************* + +/// Despacha y ejecuta las funciones de las acciones de tipo `A` que no tienen referente. /// -/// Permite recorrer de forma segura y ordenada (por peso) la lista de funciones asociadas a una -/// acción específica. +/// Recorre de forma segura y ordenada (por peso) la lista de funciones registradas para esa acción. +/// +/// Sólo alcanza las acciones que no declaran un [`ActionReferer`](super::ActionReferer) en +/// [`ActionDispatcher::referer()`]. Las que actúan sobre un tipo de objeto (y, opcionalmente, una +/// instancia concreta) se despachan con [`dispatch_referer()`]. /// /// # Parámetros genéricos /// @@ -50,28 +164,22 @@ pub(crate) fn add_action(action: ActionBox) { /// # Ejemplo /// /// ```rust,ignore -/// pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { -/// dispatch_actions( -/// &ActionKey::new( -/// UniqueId::of::(), -/// Some(UniqueId::of::()), -/// None, -/// ), -/// |action: &Self| (action.f)(component, cx), -/// ); +/// pub(crate) fn dispatch(page: &mut Page) { +/// dispatch_actions(|action: &Self| (action.f)(page)); /// } /// ``` -pub fn dispatch_actions(key: &ActionKey, f: F) +pub fn dispatch_actions(f: F) where A: ActionDispatcher, F: FnMut(&A), { - if let Some(list) = ACTIONS.read().get(key) { - list.for_each(f); + if let Some(entry) = action_entry(UniqueId::of::(), None) { + entry.general().for_each(f); } } -/// Despacha las funciones asociadas a una [`ActionKey`] con posible salida anticipada. +/// Despacha las funciones de las acciones de tipo `A` que no tienen referente, con posible salida +/// anticipada. /// /// Funciona igual que [`dispatch_actions`], pero el closure puede devolver /// [`std::ops::ControlFlow::Continue`] para continuar ejecutando la siguiente acción; o @@ -82,26 +190,70 @@ where /// ```rust,ignore /// pub(crate) fn check(cx: &Context, key: &str) -> bool { /// let mut granted = false; -/// try_dispatch_actions( -/// &ActionKey::new(UniqueId::of::(), None, None), -/// |action: &Self| { -/// (action.f)(cx, key, &mut granted); -/// if granted { -/// std::ops::ControlFlow::Break(()) -/// } else { -/// std::ops::ControlFlow::Continue(()) -/// } -/// }, -/// ); +/// try_dispatch_actions(|action: &Self| { +/// (action.f)(cx, key, &mut granted); +/// if granted { +/// std::ops::ControlFlow::Break(()) +/// } else { +/// std::ops::ControlFlow::Continue(()) +/// } +/// }); /// granted /// } /// ``` -pub fn try_dispatch_actions(key: &ActionKey, f: F) +pub fn try_dispatch_actions(f: F) where A: ActionDispatcher, F: FnMut(&A) -> std::ops::ControlFlow<()>, { - if let Some(list) = ACTIONS.read().get(key) { - list.try_for_each(f); + if let Some(entry) = action_entry(UniqueId::of::(), None) { + entry.general().try_for_each(f); + } +} + +/// Despacha las funciones de las acciones de tipo `A` asociadas a un referente de tipo `R` (p. ej. +/// un componente). +/// +/// Se aplican primero las acciones del tipo `R` y después las que sólo afectan al referente con el +/// identificador que devuelva `id`, cada lista ordenada por peso. Sólo se llama a `id` (que suele +/// reservar memoria) si hay alguna acción filtrada por identificador para `R`. +/// +/// El referente se presta a `id` y a `f` por turnos, así que `f` puede modificarlo. +/// +/// # Parámetros genéricos +/// +/// - `A`: Tipo de acción que esperamos procesar. Debe implementar [`ActionDispatcher`]. +/// - `R`: Tipo del referente, el mismo que declara [`ActionDispatcher::referer()`] en su +/// [`ActionReferer`]. +/// +/// # Ejemplo +/// +/// ```rust,ignore +/// pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { +/// dispatch_referer( +/// component, +/// |c| c.id(), +/// |action: &Self, c| (action.f)(c, cx), +/// ); +/// } +/// ``` +pub fn dispatch_referer( + referer: &mut R, + id: impl FnOnce(&R) -> Option, + mut f: impl FnMut(&A, &mut R), +) where + A: ActionDispatcher, + R: 'static, +{ + // Sin ninguna acción registrada para este tipo de referente no hay nada que hacer. + let Some(entry) = action_entry(UniqueId::of::(), Some(UniqueId::of::())) else { + return; + }; + entry.general().for_each(|action: &A| f(action, referer)); + if entry.has_ids() + && let Some(id) = id(referer) + && let Some(list) = entry.with_id(&id) + { + list.for_each(|action: &A| f(action, referer)); } } diff --git a/src/core/action/definition.rs b/src/core/action/definition.rs index 329f2648..fd73cebd 100644 --- a/src/core/action/definition.rs +++ b/src/core/action/definition.rs @@ -1,57 +1,68 @@ use crate::core::AnyInfo; -use crate::{UniqueId, Weight}; +use crate::{Getters, UniqueId, Weight, util}; /// Tipo dinámico para encapsular cualquier acción que implementa [`ActionDispatcher`]. pub type ActionBox = Box; -/// Clave para registrar las acciones y seleccionar las funciones asociadas. +/// Referente de una acción: el tipo de objeto sobre el que actúa (p. ej. un tipo de componente) y, +/// opcionalmente, el identificador de una instancia concreta. /// -/// Las funciones seleccionadas se van a [despachar](crate::core::action::dispatch_actions) y -/// ejecutar en un punto concreto del flujo de ejecución. -#[derive(Eq, PartialEq, Hash)] -pub struct ActionKey { - action_type_id: UniqueId, - referer_type_id: Option, - referer_id: Option, +/// Las acciones con referente se despachan con [`dispatch_referer()`]. Sin identificador afectan a +/// cualquier objeto del tipo; con identificador, sólo al que lo tiene. +/// +/// # Ejemplo +/// +/// ```rust +/// # use pagetop::prelude::*; +/// let any_button = ActionReferer::of::