total_space/
macros.rs

1// FILE MAYBE TESTED
2
3/// A macro for implementing data-like (states, payload) for a `struct` with a name field.
4///
5/// Usage is `impl_struct_data! { StructName = default value, "from" => "to", ... }`
6/// to implement all the boilerplate for a `struct` named `StructName`, providing it with
7/// a default value and displaying it using the results of `Debug` patched by replacing
8/// each of the (optional) `"from"` string(s) with its matching `"to"` string.
9///
10/// The `struct` type needs to `#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]`. It needs to
11/// have a `name` field which is a simple `enum` using The `struct` type needs to
12/// `#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, strum::IntoStaticStr)]`, which is used as
13/// the short name for the state (e.g. in condensed diagrams). Additional fields would be used to
14/// carry detailed state data.
15#[macro_export]
16macro_rules! impl_struct_data {
17    ($name:ident = $value:expr $(, $from:literal => $to:literal)* $(,)?) => {
18        impl_name_by_member! { $name }
19        impl_default_by_value! { $name = $value }
20        impl_display_by_patched_debug! { $name $(, $from => $to)* }
21    };
22}
23
24/// A macro for extracting static string name from a struct.
25#[doc(hidden)]
26#[macro_export]
27macro_rules! impl_name_by_member {
28    ($name:ident) => {
29        impl total_space::Name for $name {
30            fn name(&self) -> String {
31                let name: &'static str = std::convert::From::from(self.name);
32                name.to_string()
33            }
34        }
35    };
36}
37
38/// A macro for implementing data-like (states, payload) for an `enum`.
39///
40/// Usage is `impl_struct_data! { EnumName = default value, "from" => "to", ... }`
41/// to implement all the boilerplate for an `enum` named `EnumName`, providing it with
42/// a default value and displaying it using the results of `Debug` patched by replacing
43/// each of the (optional) `"from"` string(s) with its matching `"to"` string.
44///
45/// The `enum` type needs to `#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug,
46/// strum::IntoStaticStr)]`. The variant name will be used as the short name for the state (e.g. in
47/// condensed diagrams). Additional fields would be used to carry detailed state data.
48#[macro_export]
49macro_rules! impl_enum_data {
50    ($name:ident = $value:expr $(, $from:literal => $to:literal)* $(,)?) => {
51        impl_default_by_value! { $name = $value }
52        impl_name_for_into_static_str! { $name $(, $from => $to)* }
53        impl_display_by_patched_debug! { $name, "name=" => "" $(, $from => $to)* }
54    };
55}
56
57/// A macro for implementing `Default` for types using a simple value.
58#[doc(hidden)]
59#[macro_export]
60macro_rules! impl_default_by_value {
61    ($name:ident = $value:expr) => {
62        impl Default for $name {
63            fn default() -> Self {
64                $value
65            }
66        }
67    };
68}
69
70/// A macro for implementing `Name` for enums annotated by `strum::IntoStaticStr`.
71///
72/// This should become unnecessary once `IntoStaticStr` allows converting a reference to a static
73/// string, see `<https://github.com/Peternator7/strum/issues/142>`.
74#[doc(hidden)]
75#[macro_export]
76macro_rules! impl_name_for_into_static_str {
77    ($name:ident $(, $from:literal => $to:literal)* $(,)?) => {
78        impl total_space::Name for $name {
79            fn name(&self) -> String {
80                let static_str: &'static str = self.into();
81                let string = static_str.to_string();
82                $(
83                    let string = string.replace($from, $to);
84                )*
85                string
86            }
87        }
88    };
89}
90
91/// A macro for implementing `Debug` for data which has `DisplayDebug`.
92///
93/// This should be concerted to a derive macro.
94#[doc(hidden)]
95#[macro_export]
96macro_rules! impl_display_by_patched_debug {
97    ($name:ident $(, $from:literal => $to:literal)* $(,)?) => {
98        impl Display for $name {
99            fn fmt(&self, formatter: &mut Formatter<'_>) -> FormatterResult {
100                let string = format!("{:?}", self)
101                    .replace(" ", "")
102                    .replace(":", "=")
103                    .replace("{", "(")
104                    .replace("}", ")");
105                $(
106                    let string = string.replace($from, $to);
107                )*
108                write!(formatter, "{}", string)
109            }
110        }
111    };
112}
113
114/// A macro for declaring a global variable containing an agent type.
115///
116/// Usage is `declare_agent_type_data! { TYPE_NAME, StateTypeName, ModelTypeName }`. It declares a
117/// global variable named `TYPE_NAME` which contains
118/// `std::cell::RefCell<Option<std::rc::Rc<AgentTypeData::<StateTypeName, <ModelTypeName as
119/// MetaModel>::StateId, <ModelTypeName as MetaModel>::Payload >>>`, the details of which shouldn't
120/// interest you much since other macros access this in a friendly way.
121#[macro_export]
122macro_rules! declare_agent_type_data {
123    ($name:ident, $agent:ident, $model:ident) => {
124        std::thread_local! {
125            static $name: std::cell::RefCell<
126                Option<
127                   std::rc::Rc<
128                        AgentTypeData::<
129                            $agent,
130                            <$model as MetaModel>::StateId,
131                            <$model as MetaModel>::Payload,
132                        >
133                    >
134                >
135            > = std::cell::RefCell::new(None);
136        }
137    };
138}
139
140/// A macro for initializing a global variable containing an agent type.
141///
142/// This initializes the variable declared by `declare_agent_type_data`. Usage is
143/// `init_agent_type_data! { TYPE_NAME, agent_type }` passing it the the
144/// `agent_type` created to initialize the model.
145#[macro_export]
146macro_rules! init_agent_type_data {
147    ($name:ident, $agent_type:expr) => {
148        $name.with(|data| *data.borrow_mut() = Some($agent_type.clone()))
149    };
150}
151
152/// A macro for declaring a global variable containing agent indices.
153///
154/// Usage is `declare_agent_indices! { NAME }`. It declares a global variable named `NAME` to hold
155/// the agent indices for instances of some (non-singleton) agent type.
156#[macro_export]
157macro_rules! declare_agent_indices {
158    ($name:ident) => {
159        thread_local! {
160            static $name: std::cell::RefCell<Vec<usize>> = std::cell::RefCell::new(Vec::new());
161        }
162    };
163}
164
165/// A macro for declaring a global variable containing singleton agent index.
166///
167/// Usage is `declare_agent_index! { NAME }`. It declares a global variable named `NAME` to hold the
168/// agent index of the singleton instance of some agent type.
169#[macro_export]
170macro_rules! declare_agent_index {
171    ($name:ident) => {
172        thread_local! {
173            static $name: std::cell::RefCell<usize> = std::cell::RefCell::new(usize::max_value());
174        }
175    };
176}
177
178/// A macro for initializing a global variable containing singleton agent index.
179///
180/// Usage is `init_agent_indices! { NAME, agent_type }`. It will initialize the global variable
181/// created by `declare_agent_indices` using the (non-singleton) `agent_type` created to initialize
182/// the model.
183#[macro_export]
184macro_rules! init_agent_indices {
185    ($name:ident, $agent_type:expr) => {{
186        assert!(!$agent_type.is_singleton());
187        $name.with(|refcell| {
188            let mut indices = refcell.borrow_mut();
189            indices.clear();
190            for instance in 0..$agent_type.instances_count() {
191                indices.push($agent_type.first_index() + instance);
192            }
193        });
194    }};
195}
196
197/// A macro for initializing a global variable containing singleton agent index.
198///
199/// Usage is `init_agent_index! { NAME, agent_type }`. It will initialize the global variable
200/// created by `declare_agent_index` using the (singleton) `agent_type` created to initialize the
201/// model.
202#[macro_export]
203macro_rules! init_agent_index {
204    ($name:ident, $agent_type:expr) => {
205        assert!($agent_type.is_singleton());
206        $name.with(|refcell| *refcell.borrow_mut() = $agent_type.first_index());
207    };
208}
209
210/// A macro for accessing a global variable containing agent index.
211///
212/// Usage is `agent_index!(NAME)` for singleton agent types or `agent_index!(NAME[instance])` non
213/// for singleton agent types, for accessing the global variable declared by `declare_agent_index`
214/// or `declare_agent_indices`.
215#[macro_export]
216macro_rules! agent_index {
217    ($name:ident) => {{
218        $name.with(|refcell| *refcell.borrow())
219    }};
220    ($name:ident[$index:expr]) => {
221        $name.with(|refcell| refcell.borrow()[$index])
222    };
223}
224
225/// A macro for accessing the number of agent instances.
226///
227/// Usage is `agents_count!(NAME)`. It will access the global variable declared by
228/// `declare_agent_indices` to access the number of instances of agents of some (non-singleton)
229/// type.
230#[macro_export]
231macro_rules! agents_count {
232    ($name:ident) => {
233        $name.with(|refcell| refcell.borrow().len())
234    };
235}
236
237/// A macro for activity processing one out several payloads.
238///
239/// Usage is `activity_alternatives!(payload, payload, ...)` to specify an activity that causes the
240/// agent to process one of several alternative payloads.
241#[macro_export]
242macro_rules! activity_alternatives {
243    ($payload1:expr, $payload2:expr $(,)?) => {
244        total_space::Activity::Process1Of([
245            Some($payload1),
246            Some($payload2),
247            None,
248            None,
249            None,
250            None,
251        ])
252    };
253    ($payload1:expr, $payload2:expr $(,)?) => {
254        total_space::Activity::Process1Of([
255            Some($payload1),
256            Some($payload2),
257            None,
258            None,
259            None,
260            None,
261        ])
262    };
263    ($payload1:expr, $payload2:expr, $payload3:expr $(,)?) => {
264        total_space::Activity::Process1Of([
265            Some($payload1),
266            Some($payload2),
267            Some($payload3),
268            None,
269            None,
270            None,
271        ])
272    };
273    ($payload1:expr, $payload2:expr, $payload3:expr, $payload4:expr $(,)?) => {
274        total_space::Activity::Process1Of([
275            Some($payload1),
276            Some($payload2),
277            Some($payload3),
278            Some($payload4),
279            None,
280            None,
281        ])
282    };
283    ($payload1:expr, $payload2:expr, $payload3:expr, $payload4:expr, $payload5:expr $(,)?) => {
284        total_space::Activity::Process1Of([
285            Some($payload1),
286            Some($payload2),
287            Some($payload3),
288            Some($payload4),
289            Some($payload5),
290            None,
291        ])
292    };
293    ($payload1:expr, $payload2:expr, $payload3:expr, $payload4:expr, $payload5:expr, $payload6:expr $(,)?) => {
294        total_space::Activity::Process1Of([
295            Some($payload1),
296            Some($payload2),
297            Some($payload3),
298            Some($payload4),
299            Some($payload5),
300            Some($payload6),
301        ])
302    };
303    ($_:tt) => {
304        compile_error!("expected 2 to 6 payloads");
305    };
306}
307
308/// A macro for one of several alternatives reaction.
309///
310/// Usage is `reaction_alternatives!(action, action, ...)` to specify that the agent may take one of
311/// several alternative actions when reacting to a payload.
312#[macro_export]
313macro_rules! reaction_alternatives {
314    ($action1:expr, $action2:expr $(,)?) => {
315        total_space::Reaction::Do1Of([Some(action1), Some(action2), None, None, None, None])
316    };
317    ($action1:expr, $action2:expr, $action3:expr $(,)?) => {
318        total_space::Reaction::Do1Of([
319            Some(action1),
320            Some(action2),
321            Some(action3),
322            None,
323            None,
324            None,
325        ])
326    };
327    ($action1:expr, $action2:expr, $action3:expr, $action4:expr $(,)?) => {
328        total_space::Reaction::Do1Of([
329            Some(action1),
330            Some(action2),
331            Some(action3),
332            Some(action4),
333            None,
334            None,
335        ])
336    };
337    ($action1:expr, $action2:expr, $action3:expr, $action4:expr, $action5:expr $(,)?) => {
338        total_space::Reaction::Do1Of([
339            Some(action1),
340            Some(action2),
341            Some(action3),
342            Some(action4),
343            Some(action5),
344            None,
345        ])
346    };
347    ($action1:expr, $action2:expr, $action3:expr, $action4:expr, $action5:expr, $action6:expr $(,)?) => {
348        total_space::Reaction::Do1Of([
349            Some(action1),
350            Some(action2),
351            Some(action3),
352            Some(action4),
353            Some(action5),
354            Some(action6),
355        ])
356    };
357    ($_:tt) => {
358        compile_error!("expected 2 to 6 actions");
359    };
360}
361
362/// A macro for an action sending several messages.
363///
364/// Usage is `action_sends!(emit, emit, ...)` to create an action that emits multiple messages.
365#[macro_export]
366macro_rules! action_sends {
367    ($emit1:expr, $emit2:expr $(,)?) => {
368        total_space::Action::Sends([Some($emit1), Some($emit2), None, None, None, None])
369    };
370    ($emit1:expr, $emit2:expr, $emit3:expr $(,)?) => {
371        total_space::Action::Sends([Some($emit1), Some($emit2), Some($emit3), None, None, None])
372    };
373    ($emit1:expr, $emit2:expr, $emit3:expr, $emit4:expr $(,)?) => {
374        total_space::Action::Sends([
375            Some($emit1),
376            Some($emit2),
377            Some($emit3),
378            Some($emit4),
379            None,
380            None,
381        ])
382    };
383    ($emit1:expr, $emit2:expr, $emit3:expr, $emit4:expr, $emit5:expr $(,)?) => {
384        total_space::Action::Sends([
385            Some($emit1),
386            Some($emit2),
387            Some($emit3),
388            Some($emit4),
389            Some($emit5),
390            None,
391        ])
392    };
393    ($emit1:expr, $emit2:expr, $emit3:expr, $emit4:expr, $emit5:expr, $emit6:expr $(,)?) => {
394        total_space::Action::Sends([
395            Some($emit1),
396            Some($emit2),
397            Some($emit3),
398            Some($emit4),
399            Some($emit5),
400            Some($emit6),
401        ])
402    };
403    ($_:tt) => {
404        compile_error!("expected 2 to 6 emits");
405    };
406}
407
408/// A macro for an action changing the state and sending several messages.
409///
410/// Usage is `action_sends!(state, emit, emit, ...)` to create an action that changes the agent's
411/// state and emits multiple messages.
412#[macro_export]
413macro_rules! action_change_and_sends {
414    ($state:expr, $emit1:expr, $emit2:expr $(,)?) => {
415        total_space::Action::ChangeAndSends(
416            $state,
417            [Some($emit1), Some($emit2), None, None, None, None],
418        )
419    };
420    ($state:expr, $emit1:expr, $emit2:expr, $emit3:expr $(,)?) => {
421        total_space::Action::ChangeAndSends(
422            $state,
423            [Some($emit1), Some($emit2), Some($emit3), None, None, None],
424        )
425    };
426    ($state:expr, $emit1:expr, $emit2:expr, $emit3:expr, $emit4:expr $(,)?) => {
427        total_space::Action::ChangeAndSends(
428            $state,
429            [
430                Some($emit1),
431                Some($emit2),
432                Some($emit3),
433                Some($emit4),
434                None,
435                None,
436            ],
437        )
438    };
439    ($state:expr, $emit1:expr, $emit2:expr, $emit3:expr, $emit4:expr, $emit5:expr $(,)?) => {
440        total_space::Action::ChangeAndSends(
441            $state,
442            [
443                Some($emit1),
444                Some($emit2),
445                Some($emit3),
446                Some($emit4),
447                Some($emit5),
448                None,
449            ],
450        )
451    };
452    ($state:expr, $emit1:expr, $emit2:expr, $emit3:expr, $emit4:expr, $emit5:expr, $emit6:expr $(,)?) => {
453        total_space::Action::ChangeAndSends(
454            $state,
455            [
456                Some($emit1),
457                Some($emit2),
458                Some($emit3),
459                Some($emit4),
460                Some($emit5),
461                Some($emit6),
462            ],
463        )
464    };
465    ($_:tt) => {
466        compile_error!("expected state and 2 to 6 emits");
467    };
468}
469
470/// A macro for static assertion on the size of the configuration hash entry.
471///
472/// Usage is `assert_configuration_hash_entry_size!(ModelType, size)` to statically assert that the
473/// size of a configuration hash entry is as expected, to ensure this size is cache-friendly.
474#[macro_export]
475macro_rules! assert_configuration_hash_entry_size {
476    ($model:ident, $size:literal) => {
477        const _: usize = 0
478            - (std::mem::size_of::<<$model as MetaModel>::ConfigurationHashEntry>() != $size)
479                as usize;
480    };
481}
482
483/// A macro for iterating on all the agents of some type in a configuration.
484///
485/// Usage is `agent_states_iter!(configuration, TYPE_NAME, iterations)` to iterate on the states of
486/// the instances of the configuration's agents of the global `TYPE_NAME` variable declared by
487/// `declare_agent_type_data`. Iteration is some iterator method, as if we wrote
488/// `states.iter().iteration` (e.g., `iteration` could be `for_each(|state| ...)`.
489#[macro_export]
490macro_rules! agent_states_iter {
491    ($configuration:expr, $name:ident, $($iter:tt)*) => {
492        $name.with(|refcell| {
493            if let Some(agent_type) = refcell.borrow().as_ref() {
494                (0..agent_type.instances_count())
495                    .map(|agent_instance| {
496                        let agent_index = agent_type.first_index() + agent_instance;
497                        let state_id = $configuration.state_ids[agent_index];
498                        agent_type.get_state(state_id)
499                    })
500                    .$($iter)*
501            } else {
502                unreachable!()
503            }
504        })
505    };
506}
507
508/// A macro for iterating on all the in-flight messages configuration.
509///
510/// Usage is `messages_iter!(model, configuration, iterations)` to iterate on the in-flight messages
511/// of the configuration. Iteration is some iterator method, as if we wrote
512/// `messages.iter().iteration` (e.g., `iteration` could be `for_each(|message| ...)`.
513#[macro_export]
514macro_rules! messages_iter {
515    ($model:expr, $configuration:expr, $($iter:tt)*) => {
516        $configuration
517            .message_ids
518            .iter()
519            .take_while(|message_id| message_id.is_valid())
520            .map(|message_id| $model.get_message(*message_id))
521            .$($iter)*
522    };
523}