Añade soporte para inyectar acciones en código

This commit is contained in:
Manuel Cillero 2025-07-21 08:58:09 +02:00
parent 86e4c4f110
commit 613ab5243c
10 changed files with 316 additions and 4 deletions

66
src/core/action/all.rs Normal file
View file

@ -0,0 +1,66 @@
use crate::core::action::{ActionBox, ActionKey, ActionTrait, ActionsList};
use std::collections::HashMap;
use std::sync::{LazyLock, RwLock};
// ACCIONES ****************************************************************************************
static ACTIONS: LazyLock<RwLock<HashMap<ActionKey, ActionsList>>> =
LazyLock::new(|| RwLock::new(HashMap::new()));
// AÑADIR ACCIONES *********************************************************************************
// Registra una nueva acción en el sistema.
//
// Si ya existen acciones con la misma `ActionKey`, la acción se añade a la lista existente. Si no,
// se crea una nueva lista.
//
// # Uso típico
//
// Las extensiones llamarán a esta función durante su inicialización para instalar acciones
// personalizadas que modifiquen el comportamiento del núcleo o de otros componentes.
//
// ```rust,ignore
// add_action(Box::new(MyCustomAction::new()));
// ```
pub fn add_action(action: ActionBox) {
let key = action.key();
let mut actions = ACTIONS.write().unwrap();
if let Some(list) = actions.get_mut(&key) {
list.add(action);
} else {
let mut list = ActionsList::new();
list.add(action);
actions.insert(key, list);
}
}
// DESPLEGAR ACCIONES ******************************************************************************
/// Despacha las funciones asociadas a un [`ActionKey`] y las ejecuta.
///
/// Permite recorrer de forma segura y ordenada (por peso) la lista de funciones asociadas a una
/// acción específica.
///
/// # Parámetros genéricos
/// - `A`: Tipo de acción que esperamos procesar. Debe implementar [`ActionTrait`].
/// - `F`: Función o cierre que recibe cada acción y devuelve un valor de tipo `B`.
///
/// # Ejemplo de uso
/// ```rust,ignore
/// dispatch_actions::<MyCustomAction, _>(&some_key, |action| {
/// action.do_something();
/// });
/// ```
///
/// Esto permite a PageTop o a otros módulos aplicar lógica específica a las acciones de un contexto
/// determinado, manteniendo la flexibilidad del sistema.
pub fn dispatch_actions<A, B, F>(key: &ActionKey, f: F)
where
A: ActionTrait,
F: FnMut(&A) -> B,
{
if let Some(list) = ACTIONS.read().unwrap().get(key) {
list.iter_map(f);
}
}

View file

@ -0,0 +1,102 @@
use crate::core::AnyInfo;
use crate::{UniqueId, Weight};
/// Tipo dinámico para encapsular cualquier acción que implementa [`ActionTrait`].
pub type ActionBox = Box<dyn ActionTrait>;
/// Identifica una acción con una clave que define las condiciones de selección de las funciones
/// asociadas a esa acción.
///
/// Las funciones seleccionadas se van a [despachar](crate::core::action::dispatch_actions) y
/// ejecutar en un punto concreto del flujo de ejecución.
///
/// # Campos
///
/// - `action_type_id`: Tipo de la acción.
/// - `theme_type_id`: Opcional, filtra las funciones para un tema dado.
/// - `referer_type_id`: Opcional, filtra las funciones para un tipo dado (p.ej. para un tipo de
/// componente).
/// - `referer_id`: Opcional, filtra las funciones por el identificador de una instancia (p.ej. para
/// un formulario concreto).
#[derive(Eq, PartialEq, Hash)]
pub struct ActionKey {
action_type_id: UniqueId,
theme_type_id: Option<UniqueId>,
referer_type_id: Option<UniqueId>,
referer_id: Option<String>,
}
impl ActionKey {
/// Crea una nueva clave para un tipo de acción.
///
/// Esta clave permite seleccionar las funciones a ejecutar para ese tipo de acción con filtros
/// opcionales por tema, un tipo de referencia, o una instancia concreta según su identificador.
pub fn new(
action_type_id: UniqueId,
theme_type_id: Option<UniqueId>,
referer_type_id: Option<UniqueId>,
referer_id: Option<String>,
) -> Self {
ActionKey {
action_type_id,
theme_type_id,
referer_type_id,
referer_id,
}
}
}
/// Trait base que permite obtener la clave ([`ActionKey`]) asociada a una acción.
///
/// Implementado automáticamente para cualquier tipo que cumpla [`ActionTrait`].
pub trait ActionBase {
fn key(&self) -> ActionKey;
}
/// Interfaz común que deben implementar las acciones del código que pueden ser modificadas.
///
/// Este trait combina:
/// - [`AnyInfo`] para identificación única del tipo en tiempo de ejecución.
/// - `Send + Sync` para permitir uso concurrente seguro.
///
/// # Métodos personalizables
/// - `theme_type_id()`: Asocia la acción a un tipo concreto de tema (si aplica).
/// - `referer_type_id()`: Asocia la acción a un tipo de objeto referente (si aplica).
/// - `referer_id()`: Asocia la acción a un identificador concreto.
/// - `weight()`: Controla el orden de aplicación de acciones; valores más bajos se ejecutan antes.
pub trait ActionTrait: ActionBase + AnyInfo + Send + Sync {
/// Especifica el tipo de tema asociado. Por defecto `None`.
fn theme_type_id(&self) -> Option<UniqueId> {
None
}
/// Especifica el tipo del objeto referente. Por defecto `None`.
fn referer_type_id(&self) -> Option<UniqueId> {
None
}
/// Especifica un identificador único del objeto referente. Por defecto `None`.
fn referer_id(&self) -> Option<String> {
None
}
/// Define el peso lógico de la acción para determinar el orden de aplicación.
///
/// Acciones con pesos más bajos se aplicarán antes. Se pueden usar valores negativos. Por
/// defecto es `0`.
fn weight(&self) -> Weight {
0
}
}
// Implementación automática que construye la clave `ActionKey` a partir de los métodos definidos.
impl<A: ActionTrait> ActionBase for A {
fn key(&self) -> ActionKey {
ActionKey {
action_type_id: self.type_id(),
theme_type_id: self.theme_type_id(),
referer_type_id: self.referer_type_id(),
referer_id: self.referer_id(),
}
}
}

43
src/core/action/list.rs Normal file
View file

@ -0,0 +1,43 @@
use crate::core::action::{ActionBox, ActionTrait};
use crate::core::AnyCast;
use crate::trace;
use crate::AutoDefault;
use std::sync::RwLock;
#[derive(AutoDefault)]
pub struct ActionsList(RwLock<Vec<ActionBox>>);
impl ActionsList {
pub fn new() -> Self {
ActionsList::default()
}
pub fn add(&mut self, action: ActionBox) {
let mut list = self.0.write().unwrap();
list.push(action);
list.sort_by_key(|a| a.weight());
}
pub fn iter_map<A, B, F>(&self, mut f: F)
where
Self: Sized,
A: ActionTrait,
F: FnMut(&A) -> B,
{
let _: Vec<_> = self
.0
.read()
.unwrap()
.iter()
.rev()
.map(|a| {
if let Some(action) = (**a).downcast_ref::<A>() {
f(action);
} else {
trace::error!("Failed to downcast action of type {}", (**a).type_name());
}
})
.collect();
}
}