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
// @ 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,
{
    nut::new_activity(activity, DomainId::default(), LifecycleStatus::Active)
}

/// 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,
{
    nut::new_activity(activity, DomainId::new(domain), LifecycleStatus::Active)
}

/// 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())
}