//! Internal node types for DAG execution. //! //! Provides type erasure to store tasks with different types in a single collection. //! The public API remains fully type-safe; type erasure is purely an internal implementation detail. //! //! - **TypedNode\**: Stores a task with full type information //! - **ExecutableNode**: Trait for executing type-erased tasks use std::any::Any; use std::future::Future; use std::pin::Pin; use std::sync::Arc; use crate::error::DagError; use crate::types::NodeId; /// Type-erased listeners (for receiving inputs from dependencies) type ErasedListeners = Vec>; /// Type-erased async execution result (Arc-wrapped for efficient fanout) type ExecuteFuture = Pin, DagError>> + Send>>; /// Type-erased async execution result for TaskExecute trait (returns String errors) type TaskExecuteFuture = Pin, String>> + Send>>; /// Internal trait for executing heterogeneous tasks. /// /// This trait provides type erasure necessary for storing different task types in a single /// collection. While the public API (Task, Handle, Node) is fully type-safe, internally we /// need dynamic dispatch to execute tasks with different input/output types. /// /// This is the ONLY place where type erasure occurs - announcements are created with full type /// information and only erased at this trait boundary. /// /// This is an implementation detail and not part of the public API. pub(crate) trait ExecutableNode: Send { /// Create a type-erased announcement for this node's output. /// /// Returns a tuple of (Announcer, Announcement) both type-erased. /// The Announcer will be used by this node to broadcast its output. /// The Announcement can be used to create listeners for dependents. fn create_announcement(&self) -> (Box, Box); /// Create a type-erased listener from an announcement. /// /// This allows dependents to listen to this node's output. fn create_listener(&self, announcement: &dyn Any) -> Box; /// Execute the task with type-erased input listeners and an output announcer. /// /// The TypedNode implementation knows the concrete types and can safely downcast. /// Consumes the node and returns the output value. fn execute_with_listeners( self: Box, listeners: ErasedListeners, announcer: Box, ) -> ExecuteFuture; } /// Fully-typed node storage for a single task. /// /// Stores a task that has an `execute()` method (generated by the #[task] macro). /// Implements ExecutableNode for type erasure, allowing heterogeneous tasks to be /// stored in a single Vec. /// /// # Ownership Model /// /// The task is owned directly (no Mutex needed) and consumed during execution. /// Each task executes exactly once, taking ownership and producing an output. /// /// # Announcement Broadcasting /// /// Outputs are communicated via announcement channels for inter-task communication. /// Each task has ONE announcer that broadcasts to multiple listeners. /// The output is also returned from execute_with_listeners for central storage. pub(crate) struct TypedNode { pub(crate) id: NodeId, pub(crate) task: T, pub(crate) _output_type: std::marker::PhantomData ()>, } impl TypedNode { pub(crate) fn new(id: NodeId, task: T) -> Self { Self { id, task, _output_type: std::marker::PhantomData, } } } // Implementation for tasks created with the #[task] macro // These tasks have an execute() method generated by the macro impl ExecutableNode for TypedNode where T: TaskExecute, { fn create_announcement(&self) -> (Box, Box) { T::create_announcement_erased() } fn create_listener(&self, announcement: &dyn Any) -> Box { T::create_listener_erased(announcement) } fn execute_with_listeners( self: Box, listeners: ErasedListeners, announcer: Box, ) -> ExecuteFuture { Box::pin(async move { // Call the task's execute() method (generated by the #[task] macro) // This handles listener extraction, calling run(), and announcing let result = self.task.execute(listeners, announcer).await; match result { Ok(output_arc) => Ok(output_arc), Err(err_msg) => { // Execution failed - convert error message to DagError eprintln!("Task execution failed: {}", err_msg); Err(DagError::InvalidDependency { task_id: self.id.0 }) } } }) } } /// Trait for tasks that have an execute() method (generated by #[task] macro). /// /// This trait is automatically implemented by the macro and should not be /// implemented manually. pub trait TaskExecute: Send { /// Input type as a tuple of parameter types (for dependency checking) /// - `()` for no parameters /// - `T` for single parameter /// - `(A, B, C, ...)` for multiple parameters type Input: Send; /// Output type of the task type Output: Send + Sync + Clone; fn execute( self, listeners: Vec>, announcer: Box, ) -> TaskExecuteFuture; fn create_announcement_erased() -> (Box, Box); fn create_listener_erased(announcement: &dyn Any) -> Box; } #[cfg(test)] mod tests;