ixa/triggers/entity_count.rs
1//! Trigger criterion for the count of entities of a given type.
2//!
3//! [`EntityCountTrigger`] observes
4//! [`EntityCreatedEvent`](crate::entity::events::EntityCreatedEvent) for a single entity type and
5//! emits when the total number of entities of that type increases to the configured threshold.
6//!
7//! ## Construction
8//!
9//! ```rust,ignore
10//! EntityCountTrigger::<E>::increases_to(threshold)
11//! ```
12//!
13//! ## Observation
14//!
15//! The observation data passed to
16//! [`TriggerCriterion::emit_with`](super::TriggerCriterion::emit_with) is
17//! [`EntityCountTriggerEvent`]:
18//!
19//! ```rust,ignore
20//! pub struct EntityCountTriggerEvent<E: Entity> {
21//! pub entity_id: EntityId<E>,
22//! pub count: usize,
23//! }
24//! ```
25//!
26//! ## Semantics
27//!
28//! As entities can only be created, not destroyed, the count of entities is monotonic. Thus, this
29//! criterion does not use [`Direction`](super::Direction) or [`TriggerMode`](super::TriggerMode).
30//!
31//! - It fires when a creation makes the count equal to the threshold.
32//! - The observed count always equals the threshold the trigger was created with.
33//! - If the entity population already equals or exceeds the threshold _before_ the trigger is
34//! registered, it will never emit.
35//! - A threshold of `0` will not be reached by an entity creation and is therefore not allowed.
36//!
37//! ## Example
38//!
39//! ```rust
40//! use ixa::{Context, ContextEntitiesExt, define_entity, IxaEvent};
41//! use ixa::entity::EntityId;
42//! use ixa::triggers::{ContextTriggersExt, EntityCountTrigger, TriggerCriterion};
43//!
44//! define_entity!(Case);
45//!
46//! // The event records which case caused us to reach the threshold and
47//! // the value of the threshold itself (as `count`).
48//! #[derive(IxaEvent)]
49//! struct SecondCase {
50//! case_id: EntityId<Case>,
51//! count: usize,
52//! }
53//!
54//! let mut context = Context::new();
55//!
56//! context.register_trigger(
57//! EntityCountTrigger::increases_to(2)
58//! .emit_with(|observation| SecondCase {
59//! case_id: observation.entity_id,
60//! count: observation.count,
61//! }),
62//! );
63//!
64//! context.subscribe_to_event(|_context, _event: SecondCase| {
65//! // respond when the second Case entity is created
66//! });
67//! ```
68//!
69use std::marker::PhantomData;
70
71use super::TriggerCriterion;
72use crate::entity::events::EntityCreatedEvent;
73use crate::entity::{Entity, EntityId};
74use crate::Context;
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}