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    #[must_use]
89    pub fn increases_to(threshold: usize) -> Self {
90        assert!(threshold > 0, "threshold must be greater than 0");
91        Self {
92            threshold,
93            _entity: PhantomData,
94        }
95    }
96}
97
98impl<E: Entity> TriggerCriterion for EntityCountTrigger<E> {
99    type Observation = EntityCountTriggerEvent<E>;
100
101    fn install<F>(self, context: &mut Context, on_match: F)
102    where
103        F: Fn(&mut Context, Self::Observation) + 'static,
104    {
105        let threshold = self.threshold;
106        context.subscribe_to_event(move |context, event: EntityCreatedEvent<E>| {
107            // Avoids a call to `context.get_entity_count` at the expense of using internal implementation.
108            let count = event.entity_id.0 + 1;
109            if count == threshold {
110                on_match(
111                    context,
112                    EntityCountTriggerEvent {
113                        entity_id: event.entity_id,
114                        count,
115                    },
116                );
117            }
118        });
119    }
120}