aboutsummaryrefslogtreecommitdiff
path: root/mingling_core/src/any.rs
blob: b077fbae38fdd9f2ca38fc2267f1c6741378371c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#[cfg(feature = "general_renderer")]
use serde::Serialize;

use crate::Groupped;
use crate::error::ChainProcessError;

#[doc(hidden)]
pub mod group;

/// Any type output
///
/// Accepts any type that implements `Send + Groupped<G>`
/// After being passed into AnyOutput, it will be converted to `Box<dyn Any + Send + 'static>`
///
/// Note:
/// - If an enum value that does not belong to this type is incorrectly specified, it will be **unsafely** unwrapped by the scheduler
/// - Under the `general_renderer` feature, the passed value must ensure it implements `serde::Serialize`
/// - It is recommended to use the `pack!` macro from [mingling_macros](https://crates.io/crates/mingling_macros) to create types that can be converted to `AnyOutput`, which guarantees runtime safety
#[derive(Debug)]
pub struct AnyOutput<G> {
    pub(crate) inner: Box<dyn std::any::Any + Send + 'static>,
    pub type_id: std::any::TypeId,
    pub member_id: G,
}

impl<G> AnyOutput<G> {
    /// Create an AnyOutput from a `Send + Groupped<G> + Serialize` type
    #[cfg(feature = "general_renderer")]
    pub fn new<T>(value: T) -> Self
    where
        T: Send + Groupped<G> + Serialize + 'static,
    {
        Self {
            inner: Box::new(value),
            type_id: std::any::TypeId::of::<T>(),
            member_id: T::member_id(),
        }
    }

    /// Create an AnyOutput from a `Send + Groupped<G>` type
    #[cfg(not(feature = "general_renderer"))]
    pub fn new<T>(value: T) -> Self
    where
        T: Send + Groupped<G> + 'static,
    {
        Self {
            inner: Box::new(value),
            type_id: std::any::TypeId::of::<T>(),
            member_id: T::member_id(),
        }
    }

    /// Downcast the AnyOutput to a concrete type T
    pub fn downcast<T: 'static>(self) -> Result<T, Self> {
        if self.type_id == std::any::TypeId::of::<T>() {
            Ok(*self.inner.downcast::<T>().unwrap())
        } else {
            Err(self)
        }
    }

    /// Check if the inner value is of type T
    pub fn is<T: 'static>(&self) -> bool {
        self.type_id == std::any::TypeId::of::<T>()
    }

    /// Route the output to the next Chain
    pub fn route_chain(self) -> ChainProcess<G> {
        ChainProcess::Ok((self, Next::Chain))
    }

    /// Route the output to the Renderer, ending execution
    pub fn route_renderer(self) -> ChainProcess<G> {
        ChainProcess::Ok((self, Next::Renderer))
    }

    #[cfg(feature = "general_renderer")]
    /// Restore AnyOutput back to the original Serialize type
    pub fn restore<T: Serialize + 'static>(self) -> Option<T> {
        if self.type_id == std::any::TypeId::of::<T>() {
            match self.inner.downcast::<T>() {
                Ok(boxed) => Some(*boxed),
                Err(_) => None,
            }
        } else {
            None
        }
    }
}

impl<G> std::ops::Deref for AnyOutput<G> {
    type Target = dyn std::any::Any + Send + 'static;

    fn deref(&self) -> &Self::Target {
        &*self.inner
    }
}

impl<G> std::ops::DerefMut for AnyOutput<G> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut *self.inner
    }
}

/// Chain exec result type
///
/// Stores `Ok` and `Err` types of execution results, used to notify the scheduler what to execute next
/// - Returns `Ok((`[`AnyOutput`](./struct.AnyOutput.html)`, `[`Next::Chain`](./enum.Next.html)`))` to continue execution with this type next
/// - Returns `Ok((`[`AnyOutput`](./struct.AnyOutput.html)`, `[`Next::Renderer`](./enum.Next.html)`))` to render this type next and output to the terminal
/// - Returns `Err(`[`ChainProcessError`](./error/enum.ChainProcessError.html)`]` to terminate the program directly
pub type ChainProcess<G> = Result<(AnyOutput<G>, Next), ChainProcessError>;

/// Indicates the next step after processing
///
/// - `Chain`: Continue execution to the next chain
/// - `Renderer`: Send output to renderer and end execution
pub enum Next {
    Chain,
    Renderer,
}

impl<G> From<AnyOutput<G>> for ChainProcess<G> {
    fn from(value: AnyOutput<G>) -> Self {
        ChainProcess::Ok((value, Next::Chain))
    }
}