Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_state/src/state/mod.rs
6596 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::{event::EventRegistry, 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
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
54
EventRegistry::register_event::<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
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
110
EventRegistry::register_event::<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
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
168
EventRegistry::register_event::<StateTransitionEvent<TestComputedState>>(&mut world);
169
EventRegistry::register_event::<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
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
259
EventRegistry::register_event::<StateTransitionEvent<OtherState>>(&mut world);
260
EventRegistry::register_event::<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
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
358
EventRegistry::register_event::<StateTransitionEvent<SimpleState2>>(&mut world);
359
EventRegistry::register_event::<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
EventRegistry::register_event::<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::<Events<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::<Events<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
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
556
EventRegistry::register_event::<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::<Events<StateTransitionEvent<SimpleState>>>()
572
.len(),
573
1
574
);
575
assert_eq!(
576
world
577
.resource::<Events<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
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
587
EventRegistry::register_event::<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::<Events<StateTransitionEvent<SimpleState>>>()
603
.len(),
604
1
605
);
606
assert_eq!(
607
world
608
.resource::<Events<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
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
675
EventRegistry::register_event::<StateTransitionEvent<SimpleState2>>(&mut world);
676
EventRegistry::register_event::<StateTransitionEvent<MultiSourceComputedState>>(&mut world);
677
678
world.init_resource::<State<SimpleState>>();
679
world.init_resource::<State<SimpleState2>>();
680
681
let mut schedules = Schedules::new();
682
let mut apply_changes = Schedule::new(StateTransition);
683
SimpleState::register_state(&mut apply_changes);
684
SimpleState2::register_state(&mut apply_changes);
685
MultiSourceComputedState::register_computed_state_systems(&mut apply_changes);
686
schedules.insert(apply_changes);
687
688
world.insert_resource(schedules);
689
setup_state_transitions_in_world(&mut world);
690
691
// Initial state: SimpleState::A, SimpleState2::A1 and
692
// MultiSourceComputedState should not exist yet.
693
world.run_schedule(StateTransition);
694
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
695
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::A1);
696
assert!(!world.contains_resource::<State<MultiSourceComputedState>>());
697
698
// Change only SimpleState to B(true) - this should trigger
699
// MultiSourceComputedState.
700
world.insert_resource(NextState::Pending(SimpleState::B(true)));
701
world.run_schedule(StateTransition);
702
assert_eq!(
703
world.resource::<State<SimpleState>>().0,
704
SimpleState::B(true)
705
);
706
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::A1);
707
// The computed state should exist because SimpleState changed to
708
// B(true).
709
assert!(world.contains_resource::<State<MultiSourceComputedState>>());
710
assert_eq!(
711
world.resource::<State<MultiSourceComputedState>>().0,
712
MultiSourceComputedState::FromSimpleBTrue
713
);
714
715
// Reset SimpleState to A - computed state should be removed.
716
world.insert_resource(NextState::Pending(SimpleState::A));
717
world.run_schedule(StateTransition);
718
assert!(!world.contains_resource::<State<MultiSourceComputedState>>());
719
720
// Now change only SimpleState2 to B2 - this should also trigger
721
// MultiSourceComputedState.
722
world.insert_resource(NextState::Pending(SimpleState2::B2));
723
world.run_schedule(StateTransition);
724
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
725
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::B2);
726
// The computed state should exist because SimpleState2 changed to B2.
727
assert!(world.contains_resource::<State<MultiSourceComputedState>>());
728
assert_eq!(
729
world.resource::<State<MultiSourceComputedState>>().0,
730
MultiSourceComputedState::FromSimple2B2
731
);
732
733
// Test that changes to both states work.
734
world.insert_resource(NextState::Pending(SimpleState::B(true)));
735
world.insert_resource(NextState::Pending(SimpleState2::A1));
736
world.run_schedule(StateTransition);
737
assert_eq!(
738
world.resource::<State<MultiSourceComputedState>>().0,
739
MultiSourceComputedState::FromSimpleBTrue
740
);
741
}
742
743
// Test SubState that depends on multiple source states.
744
#[derive(PartialEq, Eq, Debug, Default, Hash, Clone)]
745
enum MultiSourceSubState {
746
#[default]
747
Active,
748
}
749
750
impl SubStates for MultiSourceSubState {
751
type SourceStates = (SimpleState, SimpleState2);
752
753
fn should_exist(
754
(simple_state, simple_state2): (SimpleState, SimpleState2),
755
) -> Option<Self> {
756
// SubState should exist when:
757
// - SimpleState is B(true), OR
758
// - SimpleState2 is B2
759
match (simple_state, simple_state2) {
760
(SimpleState::B(true), _) | (_, SimpleState2::B2) => Some(Self::Active),
761
_ => None,
762
}
763
}
764
}
765
766
impl States for MultiSourceSubState {
767
const DEPENDENCY_DEPTH: usize = <Self as SubStates>::SourceStates::SET_DEPENDENCY_DEPTH + 1;
768
}
769
770
impl FreelyMutableState for MultiSourceSubState {}
771
772
/// This test ensures that [`SubStates`] with multiple source states react
773
/// when any source changes.
774
#[test]
775
fn sub_state_with_multiple_sources_should_react_to_any_source_change() {
776
let mut world = World::new();
777
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
778
EventRegistry::register_event::<StateTransitionEvent<SimpleState2>>(&mut world);
779
EventRegistry::register_event::<StateTransitionEvent<MultiSourceSubState>>(&mut world);
780
781
world.init_resource::<State<SimpleState>>();
782
world.init_resource::<State<SimpleState2>>();
783
784
let mut schedules = Schedules::new();
785
let mut apply_changes = Schedule::new(StateTransition);
786
SimpleState::register_state(&mut apply_changes);
787
SimpleState2::register_state(&mut apply_changes);
788
MultiSourceSubState::register_sub_state_systems(&mut apply_changes);
789
schedules.insert(apply_changes);
790
791
world.insert_resource(schedules);
792
setup_state_transitions_in_world(&mut world);
793
794
// Initial state: SimpleState::A, SimpleState2::A1 and
795
// MultiSourceSubState should not exist yet.
796
world.run_schedule(StateTransition);
797
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
798
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::A1);
799
assert!(!world.contains_resource::<State<MultiSourceSubState>>());
800
801
// Change only SimpleState to B(true) - this should trigger
802
// MultiSourceSubState.
803
world.insert_resource(NextState::Pending(SimpleState::B(true)));
804
world.run_schedule(StateTransition);
805
assert_eq!(
806
world.resource::<State<SimpleState>>().0,
807
SimpleState::B(true)
808
);
809
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::A1);
810
// The sub state should exist because SimpleState changed to B(true).
811
assert!(world.contains_resource::<State<MultiSourceSubState>>());
812
813
// Reset to initial state.
814
world.insert_resource(NextState::Pending(SimpleState::A));
815
world.run_schedule(StateTransition);
816
assert!(!world.contains_resource::<State<MultiSourceSubState>>());
817
818
// Now change only SimpleState2 to B2 - this should also trigger
819
// MultiSourceSubState creation.
820
world.insert_resource(NextState::Pending(SimpleState2::B2));
821
world.run_schedule(StateTransition);
822
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
823
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::B2);
824
// The sub state should exist because SimpleState2 changed to B2.
825
assert!(world.contains_resource::<State<MultiSourceSubState>>());
826
827
// Finally, test that it works when both change simultaneously.
828
world.insert_resource(NextState::Pending(SimpleState::B(false)));
829
world.insert_resource(NextState::Pending(SimpleState2::A1));
830
world.run_schedule(StateTransition);
831
// After this transition, the state should not exist since SimpleState
832
// is B(false).
833
assert!(!world.contains_resource::<State<MultiSourceSubState>>());
834
835
// Change both at the same time.
836
world.insert_resource(NextState::Pending(SimpleState::B(true)));
837
world.insert_resource(NextState::Pending(SimpleState2::B2));
838
world.run_schedule(StateTransition);
839
assert!(world.contains_resource::<State<MultiSourceSubState>>());
840
}
841
842
#[test]
843
fn check_transition_orders() {
844
let mut world = World::new();
845
setup_state_transitions_in_world(&mut world);
846
EventRegistry::register_event::<StateTransitionEvent<SimpleState>>(&mut world);
847
EventRegistry::register_event::<StateTransitionEvent<SubState>>(&mut world);
848
EventRegistry::register_event::<StateTransitionEvent<TransitionTestingComputedState>>(
849
&mut world,
850
);
851
world.insert_resource(State(SimpleState::B(true)));
852
world.init_resource::<State<SubState>>();
853
world.insert_resource(State(TransitionTestingComputedState::IsA));
854
let mut schedules = world.remove_resource::<Schedules>().unwrap();
855
let apply_changes = schedules.get_mut(StateTransition).unwrap();
856
SimpleState::register_state(apply_changes);
857
SubState::register_sub_state_systems(apply_changes);
858
TransitionTestingComputedState::register_computed_state_systems(apply_changes);
859
860
world.init_resource::<TransitionTracker>();
861
fn register_transition(string: &'static str) -> impl Fn(ResMut<TransitionTracker>) {
862
move |mut transitions: ResMut<TransitionTracker>| transitions.0.push(string)
863
}
864
865
schedules.add_systems(
866
StateTransition,
867
register_transition("simple exit").in_set(ExitSchedules::<SimpleState>::default()),
868
);
869
schedules.add_systems(
870
StateTransition,
871
register_transition("simple transition")
872
.in_set(TransitionSchedules::<SimpleState>::default()),
873
);
874
schedules.add_systems(
875
StateTransition,
876
register_transition("simple enter").in_set(EnterSchedules::<SimpleState>::default()),
877
);
878
879
schedules.add_systems(
880
StateTransition,
881
register_transition("sub exit").in_set(ExitSchedules::<SubState>::default()),
882
);
883
schedules.add_systems(
884
StateTransition,
885
register_transition("sub transition")
886
.in_set(TransitionSchedules::<SubState>::default()),
887
);
888
schedules.add_systems(
889
StateTransition,
890
register_transition("sub enter").in_set(EnterSchedules::<SubState>::default()),
891
);
892
893
schedules.add_systems(
894
StateTransition,
895
register_transition("computed exit")
896
.in_set(ExitSchedules::<TransitionTestingComputedState>::default()),
897
);
898
schedules.add_systems(
899
StateTransition,
900
register_transition("computed transition")
901
.in_set(TransitionSchedules::<TransitionTestingComputedState>::default()),
902
);
903
schedules.add_systems(
904
StateTransition,
905
register_transition("computed enter")
906
.in_set(EnterSchedules::<TransitionTestingComputedState>::default()),
907
);
908
909
world.insert_resource(schedules);
910
911
world.run_schedule(StateTransition);
912
913
let transitions = &world.resource::<TransitionTracker>().0;
914
915
assert_eq!(transitions.len(), 9);
916
assert_eq!(transitions[0], "computed exit");
917
assert_eq!(transitions[1], "sub exit");
918
assert_eq!(transitions[2], "simple exit");
919
// Transition order is arbitrary and doesn't need testing.
920
assert_eq!(transitions[6], "simple enter");
921
assert_eq!(transitions[7], "sub enter");
922
assert_eq!(transitions[8], "computed enter");
923
}
924
}
925
926