Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_state/src/state/mod.rs
6849 views
1
mod computed_states;
2
mod freely_mutable_state;
3
mod resources;
4
mod state_set;
5
mod states;
6
mod sub_states;
7
mod transitions;
8
9
pub use bevy_state_macros::*;
10
pub use computed_states::*;
11
pub use freely_mutable_state::*;
12
pub use resources::*;
13
pub use state_set::*;
14
pub use states::*;
15
pub use sub_states::*;
16
pub use transitions::*;
17
18
#[cfg(test)]
19
mod tests {
20
use alloc::vec::Vec;
21
use bevy_ecs::{message::MessageRegistry, prelude::*};
22
use bevy_state_macros::{States, SubStates};
23
24
use super::*;
25
26
#[derive(States, PartialEq, Eq, Debug, Default, Hash, Clone)]
27
enum SimpleState {
28
#[default]
29
A,
30
B(bool),
31
}
32
33
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
34
enum TestComputedState {
35
BisTrue,
36
BisFalse,
37
}
38
39
impl ComputedStates for TestComputedState {
40
type SourceStates = Option<SimpleState>;
41
42
fn compute(sources: Option<SimpleState>) -> Option<Self> {
43
sources.and_then(|source| match source {
44
SimpleState::A => None,
45
SimpleState::B(value) => Some(if value { Self::BisTrue } else { Self::BisFalse }),
46
})
47
}
48
}
49
50
#[test]
51
fn computed_state_with_a_single_source_is_correctly_derived() {
52
let mut world = World::new();
53
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
54
MessageRegistry::register_message::<StateTransitionEvent<TestComputedState>>(&mut world);
55
world.init_resource::<State<SimpleState>>();
56
let mut schedules = Schedules::new();
57
let mut apply_changes = Schedule::new(StateTransition);
58
TestComputedState::register_computed_state_systems(&mut apply_changes);
59
SimpleState::register_state(&mut apply_changes);
60
schedules.insert(apply_changes);
61
62
world.insert_resource(schedules);
63
64
setup_state_transitions_in_world(&mut world);
65
66
world.run_schedule(StateTransition);
67
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
68
assert!(!world.contains_resource::<State<TestComputedState>>());
69
70
world.insert_resource(NextState::Pending(SimpleState::B(true)));
71
world.run_schedule(StateTransition);
72
assert_eq!(
73
world.resource::<State<SimpleState>>().0,
74
SimpleState::B(true)
75
);
76
assert_eq!(
77
world.resource::<State<TestComputedState>>().0,
78
TestComputedState::BisTrue
79
);
80
81
world.insert_resource(NextState::Pending(SimpleState::B(false)));
82
world.run_schedule(StateTransition);
83
assert_eq!(
84
world.resource::<State<SimpleState>>().0,
85
SimpleState::B(false)
86
);
87
assert_eq!(
88
world.resource::<State<TestComputedState>>().0,
89
TestComputedState::BisFalse
90
);
91
92
world.insert_resource(NextState::Pending(SimpleState::A));
93
world.run_schedule(StateTransition);
94
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
95
assert!(!world.contains_resource::<State<TestComputedState>>());
96
}
97
98
#[derive(SubStates, PartialEq, Eq, Debug, Default, Hash, Clone)]
99
#[source(SimpleState = SimpleState::B(true))]
100
enum SubState {
101
#[default]
102
One,
103
Two,
104
}
105
106
#[test]
107
fn sub_state_exists_only_when_allowed_but_can_be_modified_freely() {
108
let mut world = World::new();
109
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
110
MessageRegistry::register_message::<StateTransitionEvent<SubState>>(&mut world);
111
world.init_resource::<State<SimpleState>>();
112
let mut schedules = Schedules::new();
113
let mut apply_changes = Schedule::new(StateTransition);
114
SubState::register_sub_state_systems(&mut apply_changes);
115
SimpleState::register_state(&mut apply_changes);
116
schedules.insert(apply_changes);
117
118
world.insert_resource(schedules);
119
120
setup_state_transitions_in_world(&mut world);
121
122
world.run_schedule(StateTransition);
123
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
124
assert!(!world.contains_resource::<State<SubState>>());
125
126
world.insert_resource(NextState::Pending(SubState::Two));
127
world.run_schedule(StateTransition);
128
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
129
assert!(!world.contains_resource::<State<SubState>>());
130
131
world.insert_resource(NextState::Pending(SimpleState::B(true)));
132
world.run_schedule(StateTransition);
133
assert_eq!(
134
world.resource::<State<SimpleState>>().0,
135
SimpleState::B(true)
136
);
137
assert_eq!(world.resource::<State<SubState>>().0, SubState::One);
138
139
world.insert_resource(NextState::Pending(SubState::Two));
140
world.run_schedule(StateTransition);
141
assert_eq!(
142
world.resource::<State<SimpleState>>().0,
143
SimpleState::B(true)
144
);
145
assert_eq!(world.resource::<State<SubState>>().0, SubState::Two);
146
147
world.insert_resource(NextState::Pending(SimpleState::B(false)));
148
world.run_schedule(StateTransition);
149
assert_eq!(
150
world.resource::<State<SimpleState>>().0,
151
SimpleState::B(false)
152
);
153
assert!(!world.contains_resource::<State<SubState>>());
154
}
155
156
#[derive(SubStates, PartialEq, Eq, Debug, Default, Hash, Clone)]
157
#[source(TestComputedState = TestComputedState::BisTrue)]
158
enum SubStateOfComputed {
159
#[default]
160
One,
161
Two,
162
}
163
164
#[test]
165
fn substate_of_computed_states_works_appropriately() {
166
let mut world = World::new();
167
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
168
MessageRegistry::register_message::<StateTransitionEvent<TestComputedState>>(&mut world);
169
MessageRegistry::register_message::<StateTransitionEvent<SubStateOfComputed>>(&mut world);
170
world.init_resource::<State<SimpleState>>();
171
let mut schedules = Schedules::new();
172
let mut apply_changes = Schedule::new(StateTransition);
173
TestComputedState::register_computed_state_systems(&mut apply_changes);
174
SubStateOfComputed::register_sub_state_systems(&mut apply_changes);
175
SimpleState::register_state(&mut apply_changes);
176
schedules.insert(apply_changes);
177
178
world.insert_resource(schedules);
179
180
setup_state_transitions_in_world(&mut world);
181
182
world.run_schedule(StateTransition);
183
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
184
assert!(!world.contains_resource::<State<SubStateOfComputed>>());
185
186
world.insert_resource(NextState::Pending(SubStateOfComputed::Two));
187
world.run_schedule(StateTransition);
188
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
189
assert!(!world.contains_resource::<State<SubStateOfComputed>>());
190
191
world.insert_resource(NextState::Pending(SimpleState::B(true)));
192
world.run_schedule(StateTransition);
193
assert_eq!(
194
world.resource::<State<SimpleState>>().0,
195
SimpleState::B(true)
196
);
197
assert_eq!(
198
world.resource::<State<SubStateOfComputed>>().0,
199
SubStateOfComputed::One
200
);
201
202
world.insert_resource(NextState::Pending(SubStateOfComputed::Two));
203
world.run_schedule(StateTransition);
204
assert_eq!(
205
world.resource::<State<SimpleState>>().0,
206
SimpleState::B(true)
207
);
208
assert_eq!(
209
world.resource::<State<SubStateOfComputed>>().0,
210
SubStateOfComputed::Two
211
);
212
213
world.insert_resource(NextState::Pending(SimpleState::B(false)));
214
world.run_schedule(StateTransition);
215
assert_eq!(
216
world.resource::<State<SimpleState>>().0,
217
SimpleState::B(false)
218
);
219
assert!(!world.contains_resource::<State<SubStateOfComputed>>());
220
}
221
222
#[derive(States, PartialEq, Eq, Debug, Default, Hash, Clone)]
223
struct OtherState {
224
a_flexible_value: &'static str,
225
another_value: u8,
226
}
227
228
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
229
enum ComplexComputedState {
230
InAAndStrIsBobOrJane,
231
InTrueBAndUsizeAbove8,
232
}
233
234
impl ComputedStates for ComplexComputedState {
235
type SourceStates = (Option<SimpleState>, Option<OtherState>);
236
237
fn compute(sources: (Option<SimpleState>, Option<OtherState>)) -> Option<Self> {
238
match sources {
239
(Some(simple), Some(complex)) => {
240
if simple == SimpleState::A
241
&& (complex.a_flexible_value == "bob" || complex.a_flexible_value == "jane")
242
{
243
Some(ComplexComputedState::InAAndStrIsBobOrJane)
244
} else if simple == SimpleState::B(true) && complex.another_value > 8 {
245
Some(ComplexComputedState::InTrueBAndUsizeAbove8)
246
} else {
247
None
248
}
249
}
250
_ => None,
251
}
252
}
253
}
254
255
#[test]
256
fn complex_computed_state_gets_derived_correctly() {
257
let mut world = World::new();
258
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
259
MessageRegistry::register_message::<StateTransitionEvent<OtherState>>(&mut world);
260
MessageRegistry::register_message::<StateTransitionEvent<ComplexComputedState>>(&mut world);
261
world.init_resource::<State<SimpleState>>();
262
world.init_resource::<State<OtherState>>();
263
264
let mut schedules = Schedules::new();
265
let mut apply_changes = Schedule::new(StateTransition);
266
267
ComplexComputedState::register_computed_state_systems(&mut apply_changes);
268
269
SimpleState::register_state(&mut apply_changes);
270
OtherState::register_state(&mut apply_changes);
271
schedules.insert(apply_changes);
272
273
world.insert_resource(schedules);
274
275
setup_state_transitions_in_world(&mut world);
276
277
world.run_schedule(StateTransition);
278
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
279
assert_eq!(
280
world.resource::<State<OtherState>>().0,
281
OtherState::default()
282
);
283
assert!(!world.contains_resource::<State<ComplexComputedState>>());
284
285
world.insert_resource(NextState::Pending(SimpleState::B(true)));
286
world.run_schedule(StateTransition);
287
assert!(!world.contains_resource::<State<ComplexComputedState>>());
288
289
world.insert_resource(NextState::Pending(OtherState {
290
a_flexible_value: "felix",
291
another_value: 13,
292
}));
293
world.run_schedule(StateTransition);
294
assert_eq!(
295
world.resource::<State<ComplexComputedState>>().0,
296
ComplexComputedState::InTrueBAndUsizeAbove8
297
);
298
299
world.insert_resource(NextState::Pending(SimpleState::A));
300
world.insert_resource(NextState::Pending(OtherState {
301
a_flexible_value: "jane",
302
another_value: 13,
303
}));
304
world.run_schedule(StateTransition);
305
assert_eq!(
306
world.resource::<State<ComplexComputedState>>().0,
307
ComplexComputedState::InAAndStrIsBobOrJane
308
);
309
310
world.insert_resource(NextState::Pending(SimpleState::B(false)));
311
world.insert_resource(NextState::Pending(OtherState {
312
a_flexible_value: "jane",
313
another_value: 13,
314
}));
315
world.run_schedule(StateTransition);
316
assert!(!world.contains_resource::<State<ComplexComputedState>>());
317
}
318
319
#[derive(Resource, Default)]
320
struct ComputedStateTransitionCounter {
321
enter: usize,
322
exit: usize,
323
}
324
325
#[derive(States, PartialEq, Eq, Debug, Default, Hash, Clone)]
326
enum SimpleState2 {
327
#[default]
328
A1,
329
B2,
330
}
331
332
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
333
enum TestNewcomputedState {
334
A1,
335
B2,
336
B1,
337
}
338
339
impl ComputedStates for TestNewcomputedState {
340
type SourceStates = (Option<SimpleState>, Option<SimpleState2>);
341
342
fn compute((s1, s2): (Option<SimpleState>, Option<SimpleState2>)) -> Option<Self> {
343
match (s1, s2) {
344
(Some(SimpleState::A), Some(SimpleState2::A1)) => Some(TestNewcomputedState::A1),
345
(Some(SimpleState::B(true)), Some(SimpleState2::B2)) => {
346
Some(TestNewcomputedState::B2)
347
}
348
(Some(SimpleState::B(true)), _) => Some(TestNewcomputedState::B1),
349
_ => None,
350
}
351
}
352
}
353
354
#[test]
355
fn computed_state_transitions_are_produced_correctly() {
356
let mut world = World::new();
357
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
358
MessageRegistry::register_message::<StateTransitionEvent<SimpleState2>>(&mut world);
359
MessageRegistry::register_message::<StateTransitionEvent<TestNewcomputedState>>(&mut world);
360
world.init_resource::<State<SimpleState>>();
361
world.init_resource::<State<SimpleState2>>();
362
world.init_resource::<Schedules>();
363
364
setup_state_transitions_in_world(&mut world);
365
366
let mut schedules = world
367
.get_resource_mut::<Schedules>()
368
.expect("Schedules don't exist in world");
369
let apply_changes = schedules
370
.get_mut(StateTransition)
371
.expect("State Transition Schedule Doesn't Exist");
372
373
TestNewcomputedState::register_computed_state_systems(apply_changes);
374
375
SimpleState::register_state(apply_changes);
376
SimpleState2::register_state(apply_changes);
377
378
schedules.insert({
379
let mut schedule = Schedule::new(OnEnter(TestNewcomputedState::A1));
380
schedule.add_systems(|mut count: ResMut<ComputedStateTransitionCounter>| {
381
count.enter += 1;
382
});
383
schedule
384
});
385
386
schedules.insert({
387
let mut schedule = Schedule::new(OnExit(TestNewcomputedState::A1));
388
schedule.add_systems(|mut count: ResMut<ComputedStateTransitionCounter>| {
389
count.exit += 1;
390
});
391
schedule
392
});
393
394
schedules.insert({
395
let mut schedule = Schedule::new(OnEnter(TestNewcomputedState::B1));
396
schedule.add_systems(|mut count: ResMut<ComputedStateTransitionCounter>| {
397
count.enter += 1;
398
});
399
schedule
400
});
401
402
schedules.insert({
403
let mut schedule = Schedule::new(OnExit(TestNewcomputedState::B1));
404
schedule.add_systems(|mut count: ResMut<ComputedStateTransitionCounter>| {
405
count.exit += 1;
406
});
407
schedule
408
});
409
410
schedules.insert({
411
let mut schedule = Schedule::new(OnEnter(TestNewcomputedState::B2));
412
schedule.add_systems(|mut count: ResMut<ComputedStateTransitionCounter>| {
413
count.enter += 1;
414
});
415
schedule
416
});
417
418
schedules.insert({
419
let mut schedule = Schedule::new(OnExit(TestNewcomputedState::B2));
420
schedule.add_systems(|mut count: ResMut<ComputedStateTransitionCounter>| {
421
count.exit += 1;
422
});
423
schedule
424
});
425
426
world.init_resource::<ComputedStateTransitionCounter>();
427
428
setup_state_transitions_in_world(&mut world);
429
430
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
431
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::A1);
432
assert!(!world.contains_resource::<State<TestNewcomputedState>>());
433
434
world.insert_resource(NextState::Pending(SimpleState::B(true)));
435
world.insert_resource(NextState::Pending(SimpleState2::B2));
436
world.run_schedule(StateTransition);
437
assert_eq!(
438
world.resource::<State<TestNewcomputedState>>().0,
439
TestNewcomputedState::B2
440
);
441
assert_eq!(world.resource::<ComputedStateTransitionCounter>().enter, 1);
442
assert_eq!(world.resource::<ComputedStateTransitionCounter>().exit, 0);
443
444
world.insert_resource(NextState::Pending(SimpleState2::A1));
445
world.insert_resource(NextState::Pending(SimpleState::A));
446
world.run_schedule(StateTransition);
447
assert_eq!(
448
world.resource::<State<TestNewcomputedState>>().0,
449
TestNewcomputedState::A1
450
);
451
assert_eq!(
452
world.resource::<ComputedStateTransitionCounter>().enter,
453
2,
454
"Should Only Enter Twice"
455
);
456
assert_eq!(
457
world.resource::<ComputedStateTransitionCounter>().exit,
458
1,
459
"Should Only Exit Once"
460
);
461
462
world.insert_resource(NextState::Pending(SimpleState::B(true)));
463
world.insert_resource(NextState::Pending(SimpleState2::B2));
464
world.run_schedule(StateTransition);
465
assert_eq!(
466
world.resource::<State<TestNewcomputedState>>().0,
467
TestNewcomputedState::B2
468
);
469
assert_eq!(
470
world.resource::<ComputedStateTransitionCounter>().enter,
471
3,
472
"Should Only Enter Three Times"
473
);
474
assert_eq!(
475
world.resource::<ComputedStateTransitionCounter>().exit,
476
2,
477
"Should Only Exit Twice"
478
);
479
480
world.insert_resource(NextState::Pending(SimpleState::A));
481
world.run_schedule(StateTransition);
482
assert!(!world.contains_resource::<State<TestNewcomputedState>>());
483
assert_eq!(
484
world.resource::<ComputedStateTransitionCounter>().enter,
485
3,
486
"Should Only Enter Three Times"
487
);
488
assert_eq!(
489
world.resource::<ComputedStateTransitionCounter>().exit,
490
3,
491
"Should Only Exit Twice"
492
);
493
}
494
495
#[derive(Resource, Default, PartialEq, Debug)]
496
struct TransitionCounter {
497
exit: u8,
498
transition: u8,
499
enter: u8,
500
}
501
502
#[test]
503
fn same_state_transition_should_emit_event_and_not_run_schedules() {
504
let mut world = World::new();
505
setup_state_transitions_in_world(&mut world);
506
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
507
world.init_resource::<State<SimpleState>>();
508
let mut schedules = world.resource_mut::<Schedules>();
509
let apply_changes = schedules.get_mut(StateTransition).unwrap();
510
SimpleState::register_state(apply_changes);
511
512
let mut on_exit = Schedule::new(OnExit(SimpleState::A));
513
on_exit.add_systems(|mut c: ResMut<TransitionCounter>| c.exit += 1);
514
schedules.insert(on_exit);
515
let mut on_transition = Schedule::new(OnTransition {
516
exited: SimpleState::A,
517
entered: SimpleState::A,
518
});
519
on_transition.add_systems(|mut c: ResMut<TransitionCounter>| c.transition += 1);
520
schedules.insert(on_transition);
521
let mut on_enter = Schedule::new(OnEnter(SimpleState::A));
522
on_enter.add_systems(|mut c: ResMut<TransitionCounter>| c.enter += 1);
523
schedules.insert(on_enter);
524
world.insert_resource(TransitionCounter::default());
525
526
world.run_schedule(StateTransition);
527
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
528
assert!(world
529
.resource::<Messages<StateTransitionEvent<SimpleState>>>()
530
.is_empty());
531
532
world.insert_resource(TransitionCounter::default());
533
world.insert_resource(NextState::Pending(SimpleState::A));
534
world.run_schedule(StateTransition);
535
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
536
assert_eq!(
537
*world.resource::<TransitionCounter>(),
538
TransitionCounter {
539
exit: 0,
540
transition: 1, // Same state transitions are allowed
541
enter: 0
542
}
543
);
544
assert_eq!(
545
world
546
.resource::<Messages<StateTransitionEvent<SimpleState>>>()
547
.len(),
548
1
549
);
550
}
551
552
#[test]
553
fn same_state_transition_should_propagate_to_sub_state() {
554
let mut world = World::new();
555
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
556
MessageRegistry::register_message::<StateTransitionEvent<SubState>>(&mut world);
557
world.insert_resource(State(SimpleState::B(true)));
558
world.init_resource::<State<SubState>>();
559
let mut schedules = Schedules::new();
560
let mut apply_changes = Schedule::new(StateTransition);
561
SimpleState::register_state(&mut apply_changes);
562
SubState::register_sub_state_systems(&mut apply_changes);
563
schedules.insert(apply_changes);
564
world.insert_resource(schedules);
565
setup_state_transitions_in_world(&mut world);
566
567
world.insert_resource(NextState::Pending(SimpleState::B(true)));
568
world.run_schedule(StateTransition);
569
assert_eq!(
570
world
571
.resource::<Messages<StateTransitionEvent<SimpleState>>>()
572
.len(),
573
1
574
);
575
assert_eq!(
576
world
577
.resource::<Messages<StateTransitionEvent<SubState>>>()
578
.len(),
579
1
580
);
581
}
582
583
#[test]
584
fn same_state_transition_should_propagate_to_computed_state() {
585
let mut world = World::new();
586
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
587
MessageRegistry::register_message::<StateTransitionEvent<TestComputedState>>(&mut world);
588
world.insert_resource(State(SimpleState::B(true)));
589
world.insert_resource(State(TestComputedState::BisTrue));
590
let mut schedules = Schedules::new();
591
let mut apply_changes = Schedule::new(StateTransition);
592
SimpleState::register_state(&mut apply_changes);
593
TestComputedState::register_computed_state_systems(&mut apply_changes);
594
schedules.insert(apply_changes);
595
world.insert_resource(schedules);
596
setup_state_transitions_in_world(&mut world);
597
598
world.insert_resource(NextState::Pending(SimpleState::B(true)));
599
world.run_schedule(StateTransition);
600
assert_eq!(
601
world
602
.resource::<Messages<StateTransitionEvent<SimpleState>>>()
603
.len(),
604
1
605
);
606
assert_eq!(
607
world
608
.resource::<Messages<StateTransitionEvent<TestComputedState>>>()
609
.len(),
610
1
611
);
612
}
613
614
#[derive(Resource, Default, Debug)]
615
struct TransitionTracker(Vec<&'static str>);
616
617
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
618
enum TransitionTestingComputedState {
619
IsA,
620
IsBAndEven,
621
IsBAndOdd,
622
}
623
624
impl ComputedStates for TransitionTestingComputedState {
625
type SourceStates = (Option<SimpleState>, Option<SubState>);
626
627
fn compute(sources: (Option<SimpleState>, Option<SubState>)) -> Option<Self> {
628
match sources {
629
(Some(simple), sub) => {
630
if simple == SimpleState::A {
631
Some(Self::IsA)
632
} else if sub == Some(SubState::One) {
633
Some(Self::IsBAndOdd)
634
} else if sub == Some(SubState::Two) {
635
Some(Self::IsBAndEven)
636
} else {
637
None
638
}
639
}
640
_ => None,
641
}
642
}
643
}
644
645
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
646
enum MultiSourceComputedState {
647
FromSimpleBTrue,
648
FromSimple2B2,
649
FromBoth,
650
}
651
652
impl ComputedStates for MultiSourceComputedState {
653
type SourceStates = (SimpleState, SimpleState2);
654
655
fn compute((simple_state, simple_state2): (SimpleState, SimpleState2)) -> Option<Self> {
656
match (simple_state, simple_state2) {
657
// If both are in their special states, prioritize the "both" variant.
658
(SimpleState::B(true), SimpleState2::B2) => Some(Self::FromBoth),
659
// If only SimpleState is B(true).
660
(SimpleState::B(true), _) => Some(Self::FromSimpleBTrue),
661
// If only SimpleState2 is B2.
662
(_, SimpleState2::B2) => Some(Self::FromSimple2B2),
663
// Otherwise, no computed state.
664
_ => None,
665
}
666
}
667
}
668
669
/// This test ensures that [`ComputedStates`] with multiple source states
670
/// react when any source changes.
671
#[test]
672
fn computed_state_with_multiple_sources_should_react_to_any_source_change() {
673
let mut world = World::new();
674
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
675
MessageRegistry::register_message::<StateTransitionEvent<SimpleState2>>(&mut world);
676
MessageRegistry::register_message::<StateTransitionEvent<MultiSourceComputedState>>(
677
&mut world,
678
);
679
680
world.init_resource::<State<SimpleState>>();
681
world.init_resource::<State<SimpleState2>>();
682
683
let mut schedules = Schedules::new();
684
let mut apply_changes = Schedule::new(StateTransition);
685
SimpleState::register_state(&mut apply_changes);
686
SimpleState2::register_state(&mut apply_changes);
687
MultiSourceComputedState::register_computed_state_systems(&mut apply_changes);
688
schedules.insert(apply_changes);
689
690
world.insert_resource(schedules);
691
setup_state_transitions_in_world(&mut world);
692
693
// Initial state: SimpleState::A, SimpleState2::A1 and
694
// MultiSourceComputedState should not exist yet.
695
world.run_schedule(StateTransition);
696
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
697
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::A1);
698
assert!(!world.contains_resource::<State<MultiSourceComputedState>>());
699
700
// Change only SimpleState to B(true) - this should trigger
701
// MultiSourceComputedState.
702
world.insert_resource(NextState::Pending(SimpleState::B(true)));
703
world.run_schedule(StateTransition);
704
assert_eq!(
705
world.resource::<State<SimpleState>>().0,
706
SimpleState::B(true)
707
);
708
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::A1);
709
// The computed state should exist because SimpleState changed to
710
// B(true).
711
assert!(world.contains_resource::<State<MultiSourceComputedState>>());
712
assert_eq!(
713
world.resource::<State<MultiSourceComputedState>>().0,
714
MultiSourceComputedState::FromSimpleBTrue
715
);
716
717
// Reset SimpleState to A - computed state should be removed.
718
world.insert_resource(NextState::Pending(SimpleState::A));
719
world.run_schedule(StateTransition);
720
assert!(!world.contains_resource::<State<MultiSourceComputedState>>());
721
722
// Now change only SimpleState2 to B2 - this should also trigger
723
// MultiSourceComputedState.
724
world.insert_resource(NextState::Pending(SimpleState2::B2));
725
world.run_schedule(StateTransition);
726
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
727
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::B2);
728
// The computed state should exist because SimpleState2 changed to B2.
729
assert!(world.contains_resource::<State<MultiSourceComputedState>>());
730
assert_eq!(
731
world.resource::<State<MultiSourceComputedState>>().0,
732
MultiSourceComputedState::FromSimple2B2
733
);
734
735
// Test that changes to both states work.
736
world.insert_resource(NextState::Pending(SimpleState::B(true)));
737
world.insert_resource(NextState::Pending(SimpleState2::A1));
738
world.run_schedule(StateTransition);
739
assert_eq!(
740
world.resource::<State<MultiSourceComputedState>>().0,
741
MultiSourceComputedState::FromSimpleBTrue
742
);
743
}
744
745
// Test SubState that depends on multiple source states.
746
#[derive(PartialEq, Eq, Debug, Default, Hash, Clone)]
747
enum MultiSourceSubState {
748
#[default]
749
Active,
750
}
751
752
impl SubStates for MultiSourceSubState {
753
type SourceStates = (SimpleState, SimpleState2);
754
755
fn should_exist(
756
(simple_state, simple_state2): (SimpleState, SimpleState2),
757
) -> Option<Self> {
758
// SubState should exist when:
759
// - SimpleState is B(true), OR
760
// - SimpleState2 is B2
761
match (simple_state, simple_state2) {
762
(SimpleState::B(true), _) | (_, SimpleState2::B2) => Some(Self::Active),
763
_ => None,
764
}
765
}
766
}
767
768
impl States for MultiSourceSubState {
769
const DEPENDENCY_DEPTH: usize = <Self as SubStates>::SourceStates::SET_DEPENDENCY_DEPTH + 1;
770
}
771
772
impl FreelyMutableState for MultiSourceSubState {}
773
774
/// This test ensures that [`SubStates`] with multiple source states react
775
/// when any source changes.
776
#[test]
777
fn sub_state_with_multiple_sources_should_react_to_any_source_change() {
778
let mut world = World::new();
779
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
780
MessageRegistry::register_message::<StateTransitionEvent<SimpleState2>>(&mut world);
781
MessageRegistry::register_message::<StateTransitionEvent<MultiSourceSubState>>(&mut world);
782
783
world.init_resource::<State<SimpleState>>();
784
world.init_resource::<State<SimpleState2>>();
785
786
let mut schedules = Schedules::new();
787
let mut apply_changes = Schedule::new(StateTransition);
788
SimpleState::register_state(&mut apply_changes);
789
SimpleState2::register_state(&mut apply_changes);
790
MultiSourceSubState::register_sub_state_systems(&mut apply_changes);
791
schedules.insert(apply_changes);
792
793
world.insert_resource(schedules);
794
setup_state_transitions_in_world(&mut world);
795
796
// Initial state: SimpleState::A, SimpleState2::A1 and
797
// MultiSourceSubState should not exist yet.
798
world.run_schedule(StateTransition);
799
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
800
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::A1);
801
assert!(!world.contains_resource::<State<MultiSourceSubState>>());
802
803
// Change only SimpleState to B(true) - this should trigger
804
// MultiSourceSubState.
805
world.insert_resource(NextState::Pending(SimpleState::B(true)));
806
world.run_schedule(StateTransition);
807
assert_eq!(
808
world.resource::<State<SimpleState>>().0,
809
SimpleState::B(true)
810
);
811
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::A1);
812
// The sub state should exist because SimpleState changed to B(true).
813
assert!(world.contains_resource::<State<MultiSourceSubState>>());
814
815
// Reset to initial state.
816
world.insert_resource(NextState::Pending(SimpleState::A));
817
world.run_schedule(StateTransition);
818
assert!(!world.contains_resource::<State<MultiSourceSubState>>());
819
820
// Now change only SimpleState2 to B2 - this should also trigger
821
// MultiSourceSubState creation.
822
world.insert_resource(NextState::Pending(SimpleState2::B2));
823
world.run_schedule(StateTransition);
824
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
825
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::B2);
826
// The sub state should exist because SimpleState2 changed to B2.
827
assert!(world.contains_resource::<State<MultiSourceSubState>>());
828
829
// Finally, test that it works when both change simultaneously.
830
world.insert_resource(NextState::Pending(SimpleState::B(false)));
831
world.insert_resource(NextState::Pending(SimpleState2::A1));
832
world.run_schedule(StateTransition);
833
// After this transition, the state should not exist since SimpleState
834
// is B(false).
835
assert!(!world.contains_resource::<State<MultiSourceSubState>>());
836
837
// Change both at the same time.
838
world.insert_resource(NextState::Pending(SimpleState::B(true)));
839
world.insert_resource(NextState::Pending(SimpleState2::B2));
840
world.run_schedule(StateTransition);
841
assert!(world.contains_resource::<State<MultiSourceSubState>>());
842
}
843
844
#[test]
845
fn check_transition_orders() {
846
let mut world = World::new();
847
setup_state_transitions_in_world(&mut world);
848
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
849
MessageRegistry::register_message::<StateTransitionEvent<SubState>>(&mut world);
850
MessageRegistry::register_message::<StateTransitionEvent<TransitionTestingComputedState>>(
851
&mut world,
852
);
853
world.insert_resource(State(SimpleState::B(true)));
854
world.init_resource::<State<SubState>>();
855
world.insert_resource(State(TransitionTestingComputedState::IsA));
856
let mut schedules = world.remove_resource::<Schedules>().unwrap();
857
let apply_changes = schedules.get_mut(StateTransition).unwrap();
858
SimpleState::register_state(apply_changes);
859
SubState::register_sub_state_systems(apply_changes);
860
TransitionTestingComputedState::register_computed_state_systems(apply_changes);
861
862
world.init_resource::<TransitionTracker>();
863
fn register_transition(string: &'static str) -> impl Fn(ResMut<TransitionTracker>) {
864
move |mut transitions: ResMut<TransitionTracker>| transitions.0.push(string)
865
}
866
867
schedules.add_systems(
868
StateTransition,
869
register_transition("simple exit").in_set(ExitSchedules::<SimpleState>::default()),
870
);
871
schedules.add_systems(
872
StateTransition,
873
register_transition("simple transition")
874
.in_set(TransitionSchedules::<SimpleState>::default()),
875
);
876
schedules.add_systems(
877
StateTransition,
878
register_transition("simple enter").in_set(EnterSchedules::<SimpleState>::default()),
879
);
880
881
schedules.add_systems(
882
StateTransition,
883
register_transition("sub exit").in_set(ExitSchedules::<SubState>::default()),
884
);
885
schedules.add_systems(
886
StateTransition,
887
register_transition("sub transition")
888
.in_set(TransitionSchedules::<SubState>::default()),
889
);
890
schedules.add_systems(
891
StateTransition,
892
register_transition("sub enter").in_set(EnterSchedules::<SubState>::default()),
893
);
894
895
schedules.add_systems(
896
StateTransition,
897
register_transition("computed exit")
898
.in_set(ExitSchedules::<TransitionTestingComputedState>::default()),
899
);
900
schedules.add_systems(
901
StateTransition,
902
register_transition("computed transition")
903
.in_set(TransitionSchedules::<TransitionTestingComputedState>::default()),
904
);
905
schedules.add_systems(
906
StateTransition,
907
register_transition("computed enter")
908
.in_set(EnterSchedules::<TransitionTestingComputedState>::default()),
909
);
910
911
world.insert_resource(schedules);
912
913
world.run_schedule(StateTransition);
914
915
let transitions = &world.resource::<TransitionTracker>().0;
916
917
assert_eq!(transitions.len(), 9);
918
assert_eq!(transitions[0], "computed exit");
919
assert_eq!(transitions[1], "sub exit");
920
assert_eq!(transitions[2], "simple exit");
921
// Transition order is arbitrary and doesn't need testing.
922
assert_eq!(transitions[6], "simple enter");
923
assert_eq!(transitions[7], "sub enter");
924
assert_eq!(transitions[8], "computed enter");
925
}
926
}
927
928