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