1use std::cell::Cell;
2use std::marker::PhantomData;
3use std::rc::Rc;
4
5use super::{TriggerCriterion, TriggerMode, TriggerSpec};
6use crate::{Context, IxaEvent};
7
8pub struct TogglingTriggerCriteria<AC, DC> {
178 activation_criterion: AC,
179 deactivation_criterion: DC,
180 initially_active: bool,
181 mode: TriggerMode,
182}
183
184impl<AC, DC> TogglingTriggerCriteria<AC, DC>
185where
186 AC: TriggerCriterion,
187 DC: TriggerCriterion,
188{
189 #[must_use]
191 pub fn new(activation_criterion: AC, deactivation_criterion: DC) -> Self {
192 Self {
193 activation_criterion,
194 deactivation_criterion,
195 initially_active: false,
196 mode: TriggerMode::Repeating,
197 }
198 }
199
200 #[must_use]
204 pub fn initially_active(mut self) -> Self {
205 self.initially_active = true;
206 self
207 }
208
209 #[must_use]
214 pub fn initially_inactive(mut self) -> Self {
215 self.initially_active = false;
216 self
217 }
218
219 #[must_use]
224 pub fn once(mut self) -> Self {
225 self.mode = TriggerMode::Once;
226 self
227 }
228
229 #[must_use]
234 pub fn repeating(mut self) -> Self {
235 self.mode = TriggerMode::Repeating;
236 self
237 }
238
239 #[must_use]
241 pub fn emit_with<ActiveEv, InactiveEv, MakeActive, MakeInactive>(
242 self,
243 make_active_event: MakeActive,
244 make_inactive_event: MakeInactive,
245 ) -> TogglingTrigger<AC, DC, ActiveEv, InactiveEv, MakeActive, MakeInactive>
246 where
247 ActiveEv: IxaEvent,
248 InactiveEv: IxaEvent,
249 MakeActive: Fn(AC::Observation) -> ActiveEv + 'static,
250 MakeInactive: Fn(DC::Observation) -> InactiveEv + 'static,
251 {
252 TogglingTrigger {
253 activation_criterion: self.activation_criterion,
254 deactivation_criterion: self.deactivation_criterion,
255 make_active_event,
256 make_inactive_event,
257 initially_active: self.initially_active,
258 mode: self.mode,
259 _events: PhantomData,
260 }
261 }
262
263 #[must_use]
265 #[allow(clippy::type_complexity)]
266 pub fn emit_values<ActiveEv, InactiveEv>(
267 self,
268 active_event: ActiveEv,
269 inactive_event: InactiveEv,
270 ) -> TogglingTrigger<
271 AC,
272 DC,
273 ActiveEv,
274 InactiveEv,
275 impl Fn(AC::Observation) -> ActiveEv,
276 impl Fn(DC::Observation) -> InactiveEv,
277 >
278 where
279 ActiveEv: IxaEvent,
280 InactiveEv: IxaEvent,
281 {
282 self.emit_with(move |_| active_event, move |_| inactive_event)
283 }
284
285 #[must_use]
287 #[allow(clippy::type_complexity)]
288 pub fn emit_defaults<ActiveEv, InactiveEv>(
289 self,
290 ) -> TogglingTrigger<
291 AC,
292 DC,
293 ActiveEv,
294 InactiveEv,
295 impl Fn(AC::Observation) -> ActiveEv,
296 impl Fn(DC::Observation) -> InactiveEv,
297 >
298 where
299 ActiveEv: IxaEvent + Default,
300 InactiveEv: IxaEvent + Default,
301 {
302 self.emit_with(|_| ActiveEv::default(), |_| InactiveEv::default())
303 }
304}
305
306pub struct TogglingTrigger<AC, DC, ActiveEv, InactiveEv, MakeActive, MakeInactive> {
313 activation_criterion: AC,
314 deactivation_criterion: DC,
315 make_active_event: MakeActive,
316 make_inactive_event: MakeInactive,
317 initially_active: bool,
318 mode: TriggerMode,
319 _events: PhantomData<fn() -> (ActiveEv, InactiveEv)>,
320}
321
322impl<AC, DC, ActiveEv, InactiveEv, MakeActive, MakeInactive>
323 TogglingTrigger<AC, DC, ActiveEv, InactiveEv, MakeActive, MakeInactive>
324where
325 AC: TriggerCriterion,
326 DC: TriggerCriterion,
327 ActiveEv: IxaEvent,
328 InactiveEv: IxaEvent,
329 MakeActive: Fn(AC::Observation) -> ActiveEv + 'static,
330 MakeInactive: Fn(DC::Observation) -> InactiveEv + 'static,
331{
332 #[must_use]
342 pub fn new(
343 activation_criterion: AC,
344 make_active_event: MakeActive,
345 deactivation_criterion: DC,
346 make_inactive_event: MakeInactive,
347 ) -> Self {
348 Self {
349 activation_criterion,
350 deactivation_criterion,
351 make_active_event,
352 make_inactive_event,
353 initially_active: false,
354 mode: TriggerMode::Repeating,
355 _events: PhantomData,
356 }
357 }
358
359 #[must_use]
365 pub fn initially_active(mut self) -> Self {
366 self.initially_active = true;
367 self
368 }
369
370 #[must_use]
376 pub fn initially_inactive(mut self) -> Self {
377 self.initially_active = false;
378 self
379 }
380
381 #[must_use]
389 pub fn once(mut self) -> Self {
390 self.mode = TriggerMode::Once;
391 self
392 }
393
394 #[must_use]
399 pub fn repeating(mut self) -> Self {
400 self.mode = TriggerMode::Repeating;
401 self
402 }
403}
404
405impl<AC, DC, ActiveEv, InactiveEv, MakeActive, MakeInactive> TriggerSpec
406 for TogglingTrigger<AC, DC, ActiveEv, InactiveEv, MakeActive, MakeInactive>
407where
408 AC: TriggerCriterion,
409 DC: TriggerCriterion,
410 ActiveEv: IxaEvent,
411 InactiveEv: IxaEvent,
412 MakeActive: Fn(AC::Observation) -> ActiveEv + 'static,
413 MakeInactive: Fn(DC::Observation) -> InactiveEv + 'static,
414{
415 fn install_in_context(self, context: &mut Context) {
416 let Self {
417 activation_criterion,
418 deactivation_criterion,
419 make_active_event,
420 make_inactive_event,
421 initially_active,
422 mode,
423 _events,
424 } = self;
425
426 let active = Rc::new(Cell::new(initially_active));
427 let enabled = Rc::new(Cell::new(true));
428
429 activation_criterion.install(context, {
430 let active = Rc::clone(&active);
431 let enabled = Rc::clone(&enabled);
432 move |context, observation| {
433 if enabled.get() && !active.get() {
434 active.set(true);
435 context.emit_event(make_active_event(observation));
436 }
437 }
438 });
439
440 deactivation_criterion.install(context, {
441 let active = Rc::clone(&active);
442 let enabled = Rc::clone(&enabled);
443 move |context, observation| {
444 if enabled.get() && active.get() {
445 active.set(false);
446 context.emit_event(make_inactive_event(observation));
447 if mode == TriggerMode::Once {
448 enabled.set(false);
449 }
450 }
451 }
452 });
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use std::cell::{Cell, RefCell};
459 use std::rc::Rc;
460
461 use super::super::{
462 ContextTriggersExt, Direction, EntityCountTrigger, PropertyChangeTrigger,
463 PropertyValueCountTrigger, TimeTrigger,
464 };
465 use super::*;
466 use crate::entity::EntityId;
467 use crate::{define_entity, define_property, Context, ContextEntitiesExt, IxaEvent};
468
469 define_entity!(TogglePerson);
470 define_entity!(ToggleCase);
471
472 define_property!(
473 enum ToggleStatus {
474 Susceptible,
475 Infectious,
476 },
477 TogglePerson,
478 default_const = ToggleStatus::Susceptible
479 );
480
481 define_property!(struct ToggleAlive(bool), TogglePerson, default_const = ToggleAlive(true));
482
483 #[test]
484 fn toggling_trigger_gates_property_change_criteria() {
485 let mut context = Context::new();
486 let observed = Rc::new(RefCell::new(Vec::new()));
487
488 #[derive(IxaEvent)]
489 struct Activated {
490 previous: ToggleAlive,
491 current: ToggleAlive,
492 }
493
494 #[derive(IxaEvent)]
495 struct Deactivated {
496 previous: ToggleAlive,
497 current: ToggleAlive,
498 }
499
500 context.register_trigger(TogglingTrigger::new(
501 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(false)),
502 |event| Activated {
503 previous: event.previous,
504 current: event.current,
505 },
506 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(true)),
507 |event| Deactivated {
508 previous: event.previous,
509 current: event.current,
510 },
511 ));
512
513 context.subscribe_to_event({
514 let observed = Rc::clone(&observed);
515 move |_context, event: Activated| {
516 observed
517 .borrow_mut()
518 .push(("active", event.previous.0, event.current.0));
519 }
520 });
521 context.subscribe_to_event({
522 let observed = Rc::clone(&observed);
523 move |_context, event: Deactivated| {
524 observed
525 .borrow_mut()
526 .push(("inactive", event.previous.0, event.current.0));
527 }
528 });
529
530 let person = context.add_entity(TogglePerson).unwrap();
531 context.set_property(person, ToggleAlive(false));
532 context.set_property(person, ToggleAlive(false));
533 context.set_property(person, ToggleAlive(true));
534 context.set_property(person, ToggleAlive(true));
535 context.set_property(person, ToggleAlive(false));
536 context.execute();
537
538 assert_eq!(
539 *observed.borrow(),
540 vec![
541 ("active", true, false),
542 ("inactive", false, true),
543 ("active", true, false)
544 ]
545 );
546 }
547
548 #[test]
549 fn toggling_trigger_criteria_emit_with_builds_toggling_trigger() {
550 let mut context = Context::new();
551 let observed = Rc::new(RefCell::new(Vec::new()));
552
553 #[derive(IxaEvent)]
554 struct Activated {
555 previous: ToggleAlive,
556 current: ToggleAlive,
557 }
558
559 #[derive(IxaEvent)]
560 struct Deactivated {
561 previous: ToggleAlive,
562 current: ToggleAlive,
563 }
564
565 context.register_trigger(
566 TogglingTriggerCriteria::new(
567 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(false)),
568 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(true)),
569 )
570 .emit_with(
571 |event| Activated {
572 previous: event.previous,
573 current: event.current,
574 },
575 |event| Deactivated {
576 previous: event.previous,
577 current: event.current,
578 },
579 ),
580 );
581
582 context.subscribe_to_event({
583 let observed = Rc::clone(&observed);
584 move |_context, event: Activated| {
585 observed
586 .borrow_mut()
587 .push(("active", event.previous.0, event.current.0));
588 }
589 });
590 context.subscribe_to_event({
591 let observed = Rc::clone(&observed);
592 move |_context, event: Deactivated| {
593 observed
594 .borrow_mut()
595 .push(("inactive", event.previous.0, event.current.0));
596 }
597 });
598
599 let person = context.add_entity(TogglePerson).unwrap();
600 context.set_property(person, ToggleAlive(false));
601 context.set_property(person, ToggleAlive(true));
602 context.execute();
603
604 assert_eq!(
605 *observed.borrow(),
606 vec![("active", true, false), ("inactive", false, true)]
607 );
608 }
609
610 #[test]
611 fn toggling_trigger_can_start_active() {
612 let mut context = Context::new();
613 let observed = Rc::new(RefCell::new(Vec::new()));
614
615 #[derive(IxaEvent)]
616 struct Activated;
617
618 #[derive(IxaEvent)]
619 struct Deactivated;
620
621 context.register_trigger(
622 TogglingTrigger::new(
623 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(false)),
624 |_| Activated,
625 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(true)),
626 |_| Deactivated,
627 )
628 .initially_active(),
629 );
630
631 context.subscribe_to_event({
632 let observed = Rc::clone(&observed);
633 move |_context, _event: Activated| {
634 observed.borrow_mut().push("active");
635 }
636 });
637 context.subscribe_to_event({
638 let observed = Rc::clone(&observed);
639 move |_context, _event: Deactivated| {
640 observed.borrow_mut().push("inactive");
641 }
642 });
643
644 let person = context.add_entity(TogglePerson).unwrap();
645 context.set_property(person, ToggleAlive(false));
646 context.set_property(person, ToggleAlive(true));
647 context.set_property(person, ToggleAlive(false));
648 context.execute();
649
650 assert_eq!(*observed.borrow(), vec!["inactive", "active"]);
651 }
652
653 #[test]
654 fn toggling_trigger_once_disables_after_one_full_active_period() {
655 let mut context = Context::new();
656 let observed = Rc::new(RefCell::new(Vec::new()));
657
658 #[derive(IxaEvent)]
659 struct Activated;
660
661 #[derive(IxaEvent)]
662 struct Deactivated;
663
664 context.register_trigger(
665 TogglingTrigger::new(
666 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(false)),
667 |_| Activated,
668 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(true)),
669 |_| Deactivated,
670 )
671 .once(),
672 );
673
674 context.subscribe_to_event({
675 let observed = Rc::clone(&observed);
676 move |_context, _event: Activated| {
677 observed.borrow_mut().push("active");
678 }
679 });
680 context.subscribe_to_event({
681 let observed = Rc::clone(&observed);
682 move |_context, _event: Deactivated| {
683 observed.borrow_mut().push("inactive");
684 }
685 });
686
687 let person = context.add_entity(TogglePerson).unwrap();
688 context.set_property(person, ToggleAlive(true));
689 context.set_property(person, ToggleAlive(false));
690 context.set_property(person, ToggleAlive(false));
691 context.set_property(person, ToggleAlive(true));
692 context.set_property(person, ToggleAlive(false));
693 context.set_property(person, ToggleAlive(true));
694 context.execute();
695
696 assert_eq!(*observed.borrow(), vec!["active", "inactive"]);
697 }
698
699 #[test]
700 fn toggling_trigger_once_initially_active_disables_after_first_deactivation() {
701 let mut context = Context::new();
702 let observed = Rc::new(RefCell::new(Vec::new()));
703
704 #[derive(IxaEvent)]
705 struct Activated;
706
707 #[derive(IxaEvent)]
708 struct Deactivated;
709
710 context.register_trigger(
711 TogglingTrigger::new(
712 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(false)),
713 |_| Activated,
714 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(true)),
715 |_| Deactivated,
716 )
717 .initially_active()
718 .once(),
719 );
720
721 context.subscribe_to_event({
722 let observed = Rc::clone(&observed);
723 move |_context, _event: Activated| {
724 observed.borrow_mut().push("active");
725 }
726 });
727 context.subscribe_to_event({
728 let observed = Rc::clone(&observed);
729 move |_context, _event: Deactivated| {
730 observed.borrow_mut().push("inactive");
731 }
732 });
733
734 let person = context.add_entity(TogglePerson).unwrap();
735 context.set_property(person, ToggleAlive(false));
736 context.set_property(person, ToggleAlive(true));
737 context.set_property(person, ToggleAlive(false));
738 context.set_property(person, ToggleAlive(true));
739 context.execute();
740
741 assert_eq!(*observed.borrow(), vec!["inactive"]);
742 }
743
744 #[test]
745 fn toggling_trigger_criteria_emit_values_uses_constant_events() {
746 let mut context = Context::new();
747 let observed = Rc::new(RefCell::new(Vec::new()));
748
749 #[derive(IxaEvent)]
750 struct Activated;
751
752 #[derive(IxaEvent)]
753 struct Deactivated;
754
755 context.register_trigger(
756 TogglingTriggerCriteria::new(
757 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(false)),
758 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(true)),
759 )
760 .emit_values(Activated, Deactivated),
761 );
762
763 context.subscribe_to_event({
764 let observed = Rc::clone(&observed);
765 move |_context, _event: Activated| {
766 observed.borrow_mut().push("active");
767 }
768 });
769 context.subscribe_to_event({
770 let observed = Rc::clone(&observed);
771 move |_context, _event: Deactivated| {
772 observed.borrow_mut().push("inactive");
773 }
774 });
775
776 let person = context.add_entity(TogglePerson).unwrap();
777 context.set_property(person, ToggleAlive(false));
778 context.set_property(person, ToggleAlive(true));
779 context.execute();
780
781 assert_eq!(*observed.borrow(), vec!["active", "inactive"]);
782 }
783
784 #[test]
785 fn toggling_trigger_criteria_emit_defaults_uses_default_events() {
786 let mut context = Context::new();
787 let observed = Rc::new(RefCell::new(Vec::new()));
788
789 #[derive(Default, IxaEvent)]
790 struct Activated;
791
792 #[derive(Default, IxaEvent)]
793 struct Deactivated;
794
795 context.register_trigger(
796 TogglingTriggerCriteria::new(
797 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(false)),
798 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(true)),
799 )
800 .emit_defaults::<Activated, Deactivated>(),
801 );
802
803 context.subscribe_to_event({
804 let observed = Rc::clone(&observed);
805 move |_context, _event: Activated| {
806 observed.borrow_mut().push("active");
807 }
808 });
809 context.subscribe_to_event({
810 let observed = Rc::clone(&observed);
811 move |_context, _event: Deactivated| {
812 observed.borrow_mut().push("inactive");
813 }
814 });
815
816 let person = context.add_entity(TogglePerson).unwrap();
817 context.set_property(person, ToggleAlive(false));
818 context.set_property(person, ToggleAlive(true));
819 context.execute();
820
821 assert_eq!(*observed.borrow(), vec!["active", "inactive"]);
822 }
823
824 #[test]
825 fn toggling_trigger_criteria_transfers_initial_state_and_mode() {
826 let mut context = Context::new();
827 let observed = Rc::new(RefCell::new(Vec::new()));
828
829 #[derive(IxaEvent)]
830 struct Activated;
831
832 #[derive(IxaEvent)]
833 struct Deactivated;
834
835 context.register_trigger(
836 TogglingTriggerCriteria::new(
837 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(false)),
838 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(true)),
839 )
840 .initially_active()
841 .once()
842 .emit_values(Activated, Deactivated),
843 );
844
845 context.subscribe_to_event({
846 let observed = Rc::clone(&observed);
847 move |_context, _event: Activated| {
848 observed.borrow_mut().push("active");
849 }
850 });
851 context.subscribe_to_event({
852 let observed = Rc::clone(&observed);
853 move |_context, _event: Deactivated| {
854 observed.borrow_mut().push("inactive");
855 }
856 });
857
858 let person = context.add_entity(TogglePerson).unwrap();
859 context.set_property(person, ToggleAlive(false));
860 context.set_property(person, ToggleAlive(true));
861 context.set_property(person, ToggleAlive(false));
862 context.set_property(person, ToggleAlive(true));
863 context.execute();
864
865 assert_eq!(*observed.borrow(), vec!["inactive"]);
866 }
867
868 #[test]
869 fn toggling_trigger_supports_distinct_observation_and_event_types() {
870 let mut context = Context::new();
871 let observed_case_count = Rc::new(Cell::new(0));
872 let observed_time = Rc::new(Cell::new(0.0));
873
874 #[derive(IxaEvent)]
875 struct CasesActivated {
876 count: usize,
877 }
878
879 #[derive(IxaEvent)]
880 struct TimeDeactivated {
881 time: f64,
882 }
883
884 context.register_trigger(TogglingTrigger::new(
885 EntityCountTrigger::<ToggleCase>::increases_to(1),
886 |event| CasesActivated { count: event.count },
887 TimeTrigger::at(1.0),
888 |event| TimeDeactivated { time: event.time },
889 ));
890
891 context.subscribe_to_event({
892 let observed_case_count = Rc::clone(&observed_case_count);
893 move |_context, event: CasesActivated| {
894 observed_case_count.set(event.count);
895 }
896 });
897 context.subscribe_to_event({
898 let observed_time = Rc::clone(&observed_time);
899 move |_context, event: TimeDeactivated| {
900 observed_time.set(event.time);
901 }
902 });
903
904 context.add_entity(ToggleCase).unwrap();
905 context.execute();
906
907 assert_eq!(observed_case_count.get(), 1);
908 assert_eq!(observed_time.get(), 1.0);
909 }
910
911 #[test]
912 fn toggling_trigger_applies_property_value_count_hysteresis() {
913 let mut context = Context::new();
914 let observed = Rc::new(RefCell::new(Vec::new()));
915
916 #[derive(IxaEvent)]
917 struct Activated {
918 count: usize,
919 direction: Direction,
920 }
921
922 #[derive(IxaEvent)]
923 struct Deactivated {
924 count: usize,
925 direction: Direction,
926 }
927
928 context.register_trigger(TogglingTrigger::new(
929 PropertyValueCountTrigger::<TogglePerson, ToggleStatus>::changes_to(
930 ToggleStatus::Infectious,
931 2,
932 ),
933 |event| Activated {
934 count: event.count,
935 direction: event.direction,
936 },
937 PropertyValueCountTrigger::<TogglePerson, ToggleStatus>::changes_to(
938 ToggleStatus::Infectious,
939 4,
940 ),
941 |event| Deactivated {
942 count: event.count,
943 direction: event.direction,
944 },
945 ));
946
947 context.subscribe_to_event({
948 let observed = Rc::clone(&observed);
949 move |_context, event: Activated| {
950 observed
951 .borrow_mut()
952 .push(("active", event.count, event.direction));
953 }
954 });
955 context.subscribe_to_event({
956 let observed = Rc::clone(&observed);
957 move |_context, event: Deactivated| {
958 observed
959 .borrow_mut()
960 .push(("inactive", event.count, event.direction));
961 }
962 });
963
964 let first = context.add_entity(TogglePerson).unwrap();
965 let second = context.add_entity(TogglePerson).unwrap();
966 let third = context.add_entity(TogglePerson).unwrap();
967 let fourth = context.add_entity(TogglePerson).unwrap();
968
969 context.add_plan(0.1, move |context| {
970 context.set_property(first, ToggleStatus::Infectious);
971 });
972 context.add_plan(0.2, move |context| {
973 context.set_property(second, ToggleStatus::Infectious);
974 });
975 context.add_plan(0.3, move |context| {
976 context.set_property(second, ToggleStatus::Susceptible);
977 });
978 context.add_plan(0.4, move |context| {
979 context.set_property(second, ToggleStatus::Infectious);
980 });
981 context.add_plan(0.5, move |context| {
982 context.set_property(third, ToggleStatus::Infectious);
983 });
984 context.add_plan(0.6, move |context| {
985 context.set_property(fourth, ToggleStatus::Infectious);
986 });
987 context.add_plan(0.7, move |context| {
988 context.set_property(fourth, ToggleStatus::Susceptible);
989 });
990 context.add_plan(0.8, move |context| {
991 context.set_property(fourth, ToggleStatus::Infectious);
992 });
993 context.add_plan(0.9, move |context| {
994 context.set_property(fourth, ToggleStatus::Susceptible);
995 });
996 context.add_plan(1.0, move |context| {
997 context.set_property(third, ToggleStatus::Susceptible);
998 });
999 context.add_plan(1.1, move |context| {
1000 context.set_property(first, ToggleStatus::Susceptible);
1001 });
1002 context.add_plan(1.2, move |context| {
1003 context.set_property(first, ToggleStatus::Infectious);
1004 });
1005 context.add_plan(1.3, move |context| {
1006 context.set_property(third, ToggleStatus::Infectious);
1007 });
1008 context.add_plan(1.4, move |context| {
1009 context.set_property(fourth, ToggleStatus::Infectious);
1010 });
1011
1012 context.execute();
1013
1014 assert_eq!(
1015 *observed.borrow(),
1016 vec![
1017 ("active", 2, Direction::Increasing),
1018 ("inactive", 4, Direction::Increasing),
1019 ("active", 2, Direction::Decreasing),
1020 ("inactive", 4, Direction::Increasing),
1021 ]
1022 );
1023 }
1024
1025 #[test]
1026 fn toggling_trigger_can_report_entity_ids_from_observations() {
1027 let mut context = Context::new();
1028 let observed = Rc::new(Cell::new(None));
1029
1030 #[derive(IxaEvent)]
1031 struct Activated {
1032 entity_id: EntityId<TogglePerson>,
1033 }
1034
1035 #[derive(IxaEvent)]
1036 struct Deactivated;
1037
1038 context.register_trigger(TogglingTrigger::new(
1039 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(false)),
1040 |event| Activated {
1041 entity_id: event.entity_id,
1042 },
1043 PropertyChangeTrigger::<TogglePerson, ToggleAlive>::to(ToggleAlive(true)),
1044 |_| Deactivated,
1045 ));
1046
1047 context.subscribe_to_event({
1048 let observed = Rc::clone(&observed);
1049 move |_context, event: Activated| {
1050 observed.set(Some(event.entity_id));
1051 }
1052 });
1053
1054 let person = context.add_entity(TogglePerson).unwrap();
1055 context.set_property(person, ToggleAlive(false));
1056 context.execute();
1057
1058 assert_eq!(observed.get(), Some(person));
1059 }
1060}