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}