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 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
// @ START-DOC CRATE //! Nuts is a library that offers a simple publish-subscribe API, featuring decoupled creation of the publisher and the subscriber. //! //! ## Quick first example //! ```rust //! struct Activity; //! let activity = nuts::new_activity(Activity); //! activity.subscribe( //! |_activity, n: &usize| //! println!("Subscriber received {}", n) //! ); //! nuts::publish(17usize); //! // "Subscriber received 17" is printed //! nuts::publish(289usize); //! // "Subscriber received 289" is printed //! ``` //! //! As you can see in the example above, no explicit channel between publisher and subscriber is necessary. //! The call to `publish` is a static method that requires no state from the user. //! The connection between them is implicit because both use `usize` as message type. //! //! Nuts enables this simple API by managing all necessary state in thread-local storage. //! This is particularly useful when targeting the web. However, Nuts can be used on other platforms, too. //! In fact, Nuts has no dependencies aside from std. // @ END-DOC CRATE // code quality #![forbid(unsafe_code)] #![deny(clippy::mem_forget)] #![deny(clippy::print_stdout)] #![warn(clippy::mutex_integer)] #![warn(clippy::needless_borrow)] #![warn(clippy::needless_pass_by_value)] #![warn(clippy::unwrap_used)] // docs #![warn(missing_docs)] #![warn(clippy::doc_markdown)] #![warn(clippy::missing_errors_doc)] #![allow(clippy::needless_doctest_main)] #[macro_use] pub(crate) mod debug; mod nut; #[cfg(test)] mod test; pub use crate::nut::iac::managed_state::{DefaultDomain, DomainEnumeration, DomainState}; use core::any::Any; pub use nut::activity::*; pub use nut::iac::filter::*; use nut::iac::managed_state::*; use nut::iac::topic::*; /// Consumes a struct and registers it as an Activity. /// /// `nuts::new_activity(...)` is the simplest method to create a new activity. /// It takes only a single argument, which can be any struct instance or primitive. /// This object will be the private data for the activity. /// /// An `ActivityId` is returned, which is a handle to the newly registered activity. /// Use it to register callbacks on the activity. /// /// ### Example: // @ START-DOC NEW_ACTIVITY /// ```rust /// #[derive(Default)] /// struct MyActivity { /// round: usize /// } /// struct MyMessage { /// no: usize /// } /// /// // Create activity /// let activity = MyActivity::default(); /// // Activity moves into globally managed state, ID to handle it is returned /// let activity_id = nuts::new_activity(activity); /// /// // Add event listener that listens to published `MyMessage` types /// activity_id.subscribe( /// |my_activity, msg: &MyMessage| { /// println!("Round: {}, Message No: {}", my_activity.round, msg.no); /// my_activity.round += 1; /// } /// ); /// /// // prints "Round: 0, Message No: 1" /// nuts::publish( MyMessage { no: 1 } ); /// // prints "Round: 1, Message No: 2" /// nuts::publish( MyMessage { no: 2 } ); /// ``` // @ END-DOC NEW_ACTIVITY pub fn new_activity<A>(activity: A) -> ActivityId<A> where A: Activity, { let a = nut::new_activity(activity, DomainId::default(), LifecycleStatus::Active); #[cfg(feature = "verbose-debug-log")] debug_print!( "New activity {:?}({})", std::any::type_name::<A>(), a.id.index ); a } /// Consumes a struct that is registered as an Activity that has access to the specified domain. /// Use the returned `ActivityId` to register callbacks on the activity. /// // @ START-DOC NEW_ACTIVITY_WITH_DOMAIN /// ```rust /// use nuts::{domain_enum, DomainEnumeration}; /// /// #[derive(Default)] /// struct MyActivity; /// struct MyMessage; /// /// #[derive(Clone, Copy)] /// enum MyDomain { /// DomainA, /// DomainB, /// } /// domain_enum!(MyDomain); /// /// // Add data to domain /// nuts::store_to_domain(&MyDomain::DomainA, 42usize); /// /// // Register activity /// let activity_id = nuts::new_domained_activity(MyActivity, &MyDomain::DomainA); /// /// // Add event listener that listens to published `MyMessage` types and has also access to the domain data /// activity_id.subscribe_domained( /// |_my_activity, domain, msg: &MyMessage| { /// // borrow data from the domain /// let data = domain.try_get::<usize>(); /// assert_eq!(*data.unwrap(), 42); /// } /// ); /// /// // make sure the subscription closure is called /// nuts::publish( MyMessage ); /// ``` // @ END-DOC NEW_ACTIVITY_WITH_DOMAIN pub fn new_domained_activity<A, D>(activity: A, domain: &D) -> ActivityId<A> where A: Activity, D: DomainEnumeration, { let a = nut::new_activity(activity, DomainId::new(domain), LifecycleStatus::Active); #[cfg(feature = "verbose-debug-log")] debug_print!( "New activity {:?}({})", std::any::type_name::<A>(), a.id.index ); a } /// Puts the data object to the domain, which can be accessed by all associated activities. /// /// This function stores the data to the domain immediately if called outside of activities. /// Inside activities, it will be delayed. However, any messages published after calling this function can /// rely on the store to the domain to have completed when the corresponding subscribers are executed. pub fn store_to_domain<D, T>(domain: &D, data: T) where D: DomainEnumeration, T: core::any::Any, { nut::write_domain(domain, data) } /// Send the message to all subscribed activities /// // @ START-DOC PUBLISH /// Any instance of a struct or primitive can be published, as long as its type is known at compile-time. (The same constraint as for Activities.) /// Upon calling `nuts::publish`, all active subscriptions for the same type are executed and the published object will be shared with all of them. /// /// ### Example /// ```rust /// struct ChangeUser { user_name: String } /// pub fn main() { /// let msg = ChangeUser { user_name: "Donald Duck".to_owned() }; /// nuts::publish(msg); /// // Subscribers to messages of type `ChangeUser` will be notified /// } /// ``` // @ END-DOC PUBLISH /// ### Advanced: Understanding the Execution Order // @ START-DOC PUBLISH_ADVANCED /// When calling `nuts::publish(...)`, the message may not always be published immediately. While executing a subscription handler from previous `publish`, all new messages are queued up until the previous one is completed. /// ```rust /// struct MyActivity; /// let activity = nuts::new_activity(MyActivity); /// activity.subscribe( /// |_, msg: &usize| { /// println!("Start of {}", msg); /// if *msg < 3 { /// nuts::publish( msg + 1 ); /// } /// println!("End of {}", msg); /// } /// ); /// /// nuts::publish(0usize); /// // Output: /// // Start of 0 /// // End of 0 /// // Start of 1 /// // End of 1 /// // Start of 2 /// // End of 2 /// // Start of 3 /// // End of 3 /// ``` // @ END-DOC PUBLISH_ADVANCED pub fn publish<A: Any>(a: A) { nut::publish_custom(a) } /// Returns a future of type `NutsResponse` which will resolve after the /// message has been published and all subscribers have finished processing it. pub async fn publish_awaiting_response<A: Any>(a: A) { nut::publish_custom_and_await(a).await; } /// Publish a message to a specific activity. The same as `id.private_message()` but works without an `ActivityId`. /// /// The first type parameter must always be specified. /// It determines the receiver of the message. /// The message is ignored silently if no such activity has been registered or if it has no private channel for this message. /// /// The second type parameter can usually be deferred by the compiler, it is the type of the message to be sent. /// ### Example // @ START-DOC PUBLISH_PRIVATE /// ```rust /// struct ExampleActivity {} /// let id = nuts::new_activity(ExampleActivity {}); /// // `private_channel` works similar to `subscribe` but it owns the message. /// id.private_channel(|_activity, msg: usize| { /// assert_eq!(msg, 7); /// }); /// // `send_to` must be used instead of `publish` when using private channels. /// // Which activity receives the message is decide by the first type parameter. /// nuts::send_to::<ExampleActivity, _>(7usize); /// ``` // @ END-DOC PUBLISH_PRIVATE pub fn send_to<RECEIVER: Any, MSG: Any>(msg: MSG) { nut::send_custom::<RECEIVER, MSG>(msg) } #[cfg(debug_assertions)] /// Read some information about currently processing activities. /// This should be called inside a panic hook. /// /// This function is only available in debug mode as a runtime cost is associated with recording the necessary data at all times. /// The correct flag for conditional compilation is `#[cfg(debug_assertions)]`. /// /// # Example /// ``` /// fn add_nuts_hook() { /// #[cfg(debug_assertions)] /// let previous_hook = std::panic::take_hook(); /// std::panic::set_hook(Box::new(move |panic_info| { /// let nuts_info = nuts::panic_info(); /// #[cfg(features = "web-debug")] /// web_sys::console::error_1(&nuts_info.into()); /// #[cfg(not(features = "web-debug"))] /// eprintln!("{}", nuts_info); /// previous_hook(panic_info) /// })); /// } /// ``` pub fn panic_info() -> String { nut::nuts_panic_info() .unwrap_or_else(|| "NUTS panic hook: Failed to read panic info.".to_owned()) }