Skip to main content

ixa/triggers/
entity_count.rs

1use std::marker::PhantomData;
2
3use super::TriggerCriterion;
4use crate::entity::events::EntityCreatedEvent;
5use crate::entity::{Entity, EntityId};
6use crate::Context;
7
8/// Trigger criterion for the count of entities of a given type.
9///
10/// [`EntityCountTrigger`] observes
11/// [`EntityCreatedEvent`](crate::entity::events::EntityCreatedEvent) for a single entity type and
12/// emits when the total number of entities of that type increases to the configured threshold.
13///
14/// ## Construction
15///
16/// ```rust,ignore
17/// EntityCountTrigger::<E>::increases_to(threshold)
18/// ```
19///
20/// ## Observation
21///
22/// The observation data passed to
23/// [`TriggerCriterion::emit_with`](super::TriggerCriterion::emit_with) is
24/// [`EntityCountTriggerEvent`]:
25///
26/// ```rust,ignore
27/// pub struct EntityCountTriggerEvent<E: Entity> {
28///     pub entity_id: EntityId<E>,
29///     pub count: usize,
30/// }
31/// ```
32///
33/// ## Semantics
34///
35/// As entities can only be created, not destroyed, the count of entities is monotonic. Thus, this
36/// criterion does not use [`Direction`](super::Direction) or [`TriggerMode`](super::TriggerMode).
37///
38/// - It fires when a creation makes the count equal to the threshold.
39/// - The observed count always equals the threshold the trigger was created with.
40/// - If the entity population already equals or exceeds the threshold _before_ the trigger is
41///   registered, it will never emit.
42/// - A threshold of `0` will not be reached by an entity creation and is therefore not allowed.
43///
44/// ## Example
45///
46/// ```rust
47/// use ixa::{Context, ContextEntitiesExt, define_entity, IxaEvent};
48/// use ixa::entity::EntityId;
49/// use ixa::triggers::{ContextTriggersExt, EntityCountTrigger, TriggerCriterion};
50///
51/// define_entity!(Case);
52///
53/// // The event records which case caused us to reach the threshold and
54/// // the value of the threshold itself (as `count`).
55/// #[derive(IxaEvent)]
56/// struct SecondCase {
57///     case_id: EntityId<Case>,
58///     count: usize,
59/// }
60///
61/// let mut context = Context::new();
62///
63/// context.register_trigger(
64///     EntityCountTrigger::increases_to(2)
65///         .emit_with(|observation| SecondCase {
66///             case_id: observation.entity_id,
67///             count: observation.count,
68///         }),
69/// );
70///
71/// context.subscribe_to_event(|_context, _event: SecondCase| {
72///     // respond when the second Case entity is created
73/// });
74/// ```
75///
76pub struct EntityCountTrigger<E: Entity> {
77    threshold: usize,
78    _entity: PhantomData<fn() -> E>,
79}
80
81#[derive(Clone, Copy, Debug)]
82pub struct EntityCountTriggerEvent<E: Entity> {
83    pub entity_id: EntityId<E>,
84    pub count: usize,
85}
86
87impl<E: Entity> EntityCountTrigger<E> {
88    /// Creates a trigger that fires when the entity count reaches `threshold`.
89    /// The `threshold` must be positive.
90    ///
91    /// # Panics
92    ///
93    /// Panics if `threshold` is zero.
94    #[must_use]
95    pub fn increases_to(threshold: usize) -> Self {
96        assert!(threshold > 0, "threshold must be greater than 0");
97        Self {
98            threshold,
99            _entity: PhantomData,
100        }
101    }
102}
103
104impl<E: Entity> TriggerCriterion for EntityCountTrigger<E> {
105    type Observation = EntityCountTriggerEvent<E>;
106
107    fn install<F>(self, context: &mut Context, on_match: F)
108    where
109        F: Fn(&mut Context, Self::Observation) + 'static,
110    {
111        let threshold = self.threshold;
112        context.subscribe_to_event(move |context, event: EntityCreatedEvent<E>| {
113            // Avoids a call to `context.get_entity_count` at the expense of using internal implementation.
114            let count = event.entity_id.0 + 1;
115            if count == threshold {
116                on_match(
117                    context,
118                    EntityCountTriggerEvent {
119                        entity_id: event.entity_id,
120                        count,
121                    },
122                );
123            }
124        });
125    }
126}