Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_state/src/state/mod.rs
9401 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_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: 1,
540
transition: 1,
541
enter: 1
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_emit_event_and_not_run_schedules_if_same_state_transitions_are_disallowed(
554
) {
555
let mut world = World::new();
556
setup_state_transitions_in_world(&mut world);
557
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
558
world.init_resource::<State<SimpleState>>();
559
let mut schedules = world.resource_mut::<Schedules>();
560
let apply_changes = schedules.get_mut(StateTransition).unwrap();
561
SimpleState::register_state(apply_changes);
562
563
let mut on_exit = Schedule::new(OnExit(SimpleState::A));
564
on_exit.add_systems(|mut c: ResMut<TransitionCounter>| c.exit += 1);
565
schedules.insert(on_exit);
566
let mut on_transition = Schedule::new(OnTransition {
567
exited: SimpleState::A,
568
entered: SimpleState::A,
569
});
570
on_transition.add_systems(|mut c: ResMut<TransitionCounter>| c.transition += 1);
571
schedules.insert(on_transition);
572
let mut on_enter = Schedule::new(OnEnter(SimpleState::A));
573
on_enter.add_systems(|mut c: ResMut<TransitionCounter>| c.enter += 1);
574
schedules.insert(on_enter);
575
world.insert_resource(TransitionCounter::default());
576
577
world.run_schedule(StateTransition);
578
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
579
assert!(world
580
.resource::<Messages<StateTransitionEvent<SimpleState>>>()
581
.is_empty());
582
583
world.insert_resource(TransitionCounter::default());
584
world.insert_resource(NextState::PendingIfNeq(SimpleState::A));
585
world.run_schedule(StateTransition);
586
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
587
assert_eq!(
588
*world.resource::<TransitionCounter>(),
589
TransitionCounter {
590
exit: 0,
591
transition: 1, // Same state transitions are allowed
592
enter: 0
593
}
594
);
595
assert_eq!(
596
world
597
.resource::<Messages<StateTransitionEvent<SimpleState>>>()
598
.len(),
599
1
600
);
601
}
602
603
#[test]
604
fn same_state_transition_should_propagate_to_sub_state() {
605
let mut world = World::new();
606
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
607
MessageRegistry::register_message::<StateTransitionEvent<SubState>>(&mut world);
608
world.insert_resource(State(SimpleState::B(true)));
609
world.init_resource::<State<SubState>>();
610
let mut schedules = Schedules::new();
611
let mut apply_changes = Schedule::new(StateTransition);
612
SimpleState::register_state(&mut apply_changes);
613
SubState::register_sub_state_systems(&mut apply_changes);
614
schedules.insert(apply_changes);
615
world.insert_resource(schedules);
616
setup_state_transitions_in_world(&mut world);
617
618
world.insert_resource(NextState::Pending(SimpleState::B(true)));
619
world.run_schedule(StateTransition);
620
assert_eq!(
621
world
622
.resource::<Messages<StateTransitionEvent<SimpleState>>>()
623
.len(),
624
1
625
);
626
assert_eq!(
627
world
628
.resource::<Messages<StateTransitionEvent<SubState>>>()
629
.len(),
630
1
631
);
632
}
633
634
#[test]
635
fn same_state_transition_should_propagate_to_computed_state() {
636
let mut world = World::new();
637
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
638
MessageRegistry::register_message::<StateTransitionEvent<TestComputedState>>(&mut world);
639
world.insert_resource(State(SimpleState::B(true)));
640
world.insert_resource(State(TestComputedState::BisTrue));
641
let mut schedules = Schedules::new();
642
let mut apply_changes = Schedule::new(StateTransition);
643
SimpleState::register_state(&mut apply_changes);
644
TestComputedState::register_computed_state_systems(&mut apply_changes);
645
schedules.insert(apply_changes);
646
world.insert_resource(schedules);
647
setup_state_transitions_in_world(&mut world);
648
649
world.insert_resource(NextState::Pending(SimpleState::B(true)));
650
world.run_schedule(StateTransition);
651
assert_eq!(
652
world
653
.resource::<Messages<StateTransitionEvent<SimpleState>>>()
654
.len(),
655
1
656
);
657
assert_eq!(
658
world
659
.resource::<Messages<StateTransitionEvent<TestComputedState>>>()
660
.len(),
661
1
662
);
663
}
664
665
#[derive(Resource, Default, Debug)]
666
struct TransitionTracker(Vec<&'static str>);
667
668
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
669
enum TransitionTestingComputedState {
670
IsA,
671
IsBAndEven,
672
IsBAndOdd,
673
}
674
675
impl ComputedStates for TransitionTestingComputedState {
676
type SourceStates = (Option<SimpleState>, Option<SubState>);
677
678
fn compute(sources: (Option<SimpleState>, Option<SubState>)) -> Option<Self> {
679
match sources {
680
(Some(simple), sub) => {
681
if simple == SimpleState::A {
682
Some(Self::IsA)
683
} else if sub == Some(SubState::One) {
684
Some(Self::IsBAndOdd)
685
} else if sub == Some(SubState::Two) {
686
Some(Self::IsBAndEven)
687
} else {
688
None
689
}
690
}
691
_ => None,
692
}
693
}
694
}
695
696
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
697
enum MultiSourceComputedState {
698
FromSimpleBTrue,
699
FromSimple2B2,
700
FromBoth,
701
}
702
703
impl ComputedStates for MultiSourceComputedState {
704
type SourceStates = (SimpleState, SimpleState2);
705
706
fn compute((simple_state, simple_state2): (SimpleState, SimpleState2)) -> Option<Self> {
707
match (simple_state, simple_state2) {
708
// If both are in their special states, prioritize the "both" variant.
709
(SimpleState::B(true), SimpleState2::B2) => Some(Self::FromBoth),
710
// If only SimpleState is B(true).
711
(SimpleState::B(true), _) => Some(Self::FromSimpleBTrue),
712
// If only SimpleState2 is B2.
713
(_, SimpleState2::B2) => Some(Self::FromSimple2B2),
714
// Otherwise, no computed state.
715
_ => None,
716
}
717
}
718
}
719
720
/// This test ensures that [`ComputedStates`] with multiple source states
721
/// react when any source changes.
722
#[test]
723
fn computed_state_with_multiple_sources_should_react_to_any_source_change() {
724
let mut world = World::new();
725
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
726
MessageRegistry::register_message::<StateTransitionEvent<SimpleState2>>(&mut world);
727
MessageRegistry::register_message::<StateTransitionEvent<MultiSourceComputedState>>(
728
&mut world,
729
);
730
731
world.init_resource::<State<SimpleState>>();
732
world.init_resource::<State<SimpleState2>>();
733
734
let mut schedules = Schedules::new();
735
let mut apply_changes = Schedule::new(StateTransition);
736
SimpleState::register_state(&mut apply_changes);
737
SimpleState2::register_state(&mut apply_changes);
738
MultiSourceComputedState::register_computed_state_systems(&mut apply_changes);
739
schedules.insert(apply_changes);
740
741
world.insert_resource(schedules);
742
setup_state_transitions_in_world(&mut world);
743
744
// Initial state: SimpleState::A, SimpleState2::A1 and
745
// MultiSourceComputedState should not exist yet.
746
world.run_schedule(StateTransition);
747
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
748
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::A1);
749
assert!(!world.contains_resource::<State<MultiSourceComputedState>>());
750
751
// Change only SimpleState to B(true) - this should trigger
752
// MultiSourceComputedState.
753
world.insert_resource(NextState::Pending(SimpleState::B(true)));
754
world.run_schedule(StateTransition);
755
assert_eq!(
756
world.resource::<State<SimpleState>>().0,
757
SimpleState::B(true)
758
);
759
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::A1);
760
// The computed state should exist because SimpleState changed to
761
// B(true).
762
assert!(world.contains_resource::<State<MultiSourceComputedState>>());
763
assert_eq!(
764
world.resource::<State<MultiSourceComputedState>>().0,
765
MultiSourceComputedState::FromSimpleBTrue
766
);
767
768
// Reset SimpleState to A - computed state should be removed.
769
world.insert_resource(NextState::Pending(SimpleState::A));
770
world.run_schedule(StateTransition);
771
assert!(!world.contains_resource::<State<MultiSourceComputedState>>());
772
773
// Now change only SimpleState2 to B2 - this should also trigger
774
// MultiSourceComputedState.
775
world.insert_resource(NextState::Pending(SimpleState2::B2));
776
world.run_schedule(StateTransition);
777
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
778
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::B2);
779
// The computed state should exist because SimpleState2 changed to B2.
780
assert!(world.contains_resource::<State<MultiSourceComputedState>>());
781
assert_eq!(
782
world.resource::<State<MultiSourceComputedState>>().0,
783
MultiSourceComputedState::FromSimple2B2
784
);
785
786
// Test that changes to both states work.
787
world.insert_resource(NextState::Pending(SimpleState::B(true)));
788
world.insert_resource(NextState::Pending(SimpleState2::A1));
789
world.run_schedule(StateTransition);
790
assert_eq!(
791
world.resource::<State<MultiSourceComputedState>>().0,
792
MultiSourceComputedState::FromSimpleBTrue
793
);
794
}
795
796
// Test SubState that depends on multiple source states.
797
#[derive(PartialEq, Eq, Debug, Default, Hash, Clone)]
798
enum MultiSourceSubState {
799
#[default]
800
Active,
801
}
802
803
impl SubStates for MultiSourceSubState {
804
type SourceStates = (SimpleState, SimpleState2);
805
806
fn should_exist(
807
(simple_state, simple_state2): (SimpleState, SimpleState2),
808
) -> Option<Self> {
809
// SubState should exist when:
810
// - SimpleState is B(true), OR
811
// - SimpleState2 is B2
812
match (simple_state, simple_state2) {
813
(SimpleState::B(true), _) | (_, SimpleState2::B2) => Some(Self::Active),
814
_ => None,
815
}
816
}
817
}
818
819
impl States for MultiSourceSubState {
820
const DEPENDENCY_DEPTH: usize = <Self as SubStates>::SourceStates::SET_DEPENDENCY_DEPTH + 1;
821
}
822
823
impl FreelyMutableState for MultiSourceSubState {}
824
825
/// This test ensures that [`SubStates`] with multiple source states react
826
/// when any source changes.
827
#[test]
828
fn sub_state_with_multiple_sources_should_react_to_any_source_change() {
829
let mut world = World::new();
830
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
831
MessageRegistry::register_message::<StateTransitionEvent<SimpleState2>>(&mut world);
832
MessageRegistry::register_message::<StateTransitionEvent<MultiSourceSubState>>(&mut world);
833
834
world.init_resource::<State<SimpleState>>();
835
world.init_resource::<State<SimpleState2>>();
836
837
let mut schedules = Schedules::new();
838
let mut apply_changes = Schedule::new(StateTransition);
839
SimpleState::register_state(&mut apply_changes);
840
SimpleState2::register_state(&mut apply_changes);
841
MultiSourceSubState::register_sub_state_systems(&mut apply_changes);
842
schedules.insert(apply_changes);
843
844
world.insert_resource(schedules);
845
setup_state_transitions_in_world(&mut world);
846
847
// Initial state: SimpleState::A, SimpleState2::A1 and
848
// MultiSourceSubState should not exist yet.
849
world.run_schedule(StateTransition);
850
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
851
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::A1);
852
assert!(!world.contains_resource::<State<MultiSourceSubState>>());
853
854
// Change only SimpleState to B(true) - this should trigger
855
// MultiSourceSubState.
856
world.insert_resource(NextState::Pending(SimpleState::B(true)));
857
world.run_schedule(StateTransition);
858
assert_eq!(
859
world.resource::<State<SimpleState>>().0,
860
SimpleState::B(true)
861
);
862
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::A1);
863
// The sub state should exist because SimpleState changed to B(true).
864
assert!(world.contains_resource::<State<MultiSourceSubState>>());
865
866
// Reset to initial state.
867
world.insert_resource(NextState::Pending(SimpleState::A));
868
world.run_schedule(StateTransition);
869
assert!(!world.contains_resource::<State<MultiSourceSubState>>());
870
871
// Now change only SimpleState2 to B2 - this should also trigger
872
// MultiSourceSubState creation.
873
world.insert_resource(NextState::Pending(SimpleState2::B2));
874
world.run_schedule(StateTransition);
875
assert_eq!(world.resource::<State<SimpleState>>().0, SimpleState::A);
876
assert_eq!(world.resource::<State<SimpleState2>>().0, SimpleState2::B2);
877
// The sub state should exist because SimpleState2 changed to B2.
878
assert!(world.contains_resource::<State<MultiSourceSubState>>());
879
880
// Finally, test that it works when both change simultaneously.
881
world.insert_resource(NextState::Pending(SimpleState::B(false)));
882
world.insert_resource(NextState::Pending(SimpleState2::A1));
883
world.run_schedule(StateTransition);
884
// After this transition, the state should not exist since SimpleState
885
// is B(false).
886
assert!(!world.contains_resource::<State<MultiSourceSubState>>());
887
888
// Change both at the same time.
889
world.insert_resource(NextState::Pending(SimpleState::B(true)));
890
world.insert_resource(NextState::Pending(SimpleState2::B2));
891
world.run_schedule(StateTransition);
892
assert!(world.contains_resource::<State<MultiSourceSubState>>());
893
}
894
895
#[test]
896
fn check_transition_orders() {
897
let mut world = World::new();
898
setup_state_transitions_in_world(&mut world);
899
MessageRegistry::register_message::<StateTransitionEvent<SimpleState>>(&mut world);
900
MessageRegistry::register_message::<StateTransitionEvent<SubState>>(&mut world);
901
MessageRegistry::register_message::<StateTransitionEvent<TransitionTestingComputedState>>(
902
&mut world,
903
);
904
world.insert_resource(State(SimpleState::B(true)));
905
world.init_resource::<State<SubState>>();
906
world.insert_resource(State(TransitionTestingComputedState::IsA));
907
let mut schedules = world.remove_resource::<Schedules>().unwrap();
908
let apply_changes = schedules.get_mut(StateTransition).unwrap();
909
SimpleState::register_state(apply_changes);
910
SubState::register_sub_state_systems(apply_changes);
911
TransitionTestingComputedState::register_computed_state_systems(apply_changes);
912
913
world.init_resource::<TransitionTracker>();
914
fn register_transition(string: &'static str) -> impl Fn(ResMut<TransitionTracker>) {
915
move |mut transitions: ResMut<TransitionTracker>| transitions.0.push(string)
916
}
917
918
schedules.add_systems(
919
StateTransition,
920
register_transition("simple exit").in_set(ExitSchedules::<SimpleState>::default()),
921
);
922
schedules.add_systems(
923
StateTransition,
924
register_transition("simple transition")
925
.in_set(TransitionSchedules::<SimpleState>::default()),
926
);
927
schedules.add_systems(
928
StateTransition,
929
register_transition("simple enter").in_set(EnterSchedules::<SimpleState>::default()),
930
);
931
932
schedules.add_systems(
933
StateTransition,
934
register_transition("sub exit").in_set(ExitSchedules::<SubState>::default()),
935
);
936
schedules.add_systems(
937
StateTransition,
938
register_transition("sub transition")
939
.in_set(TransitionSchedules::<SubState>::default()),
940
);
941
schedules.add_systems(
942
StateTransition,
943
register_transition("sub enter").in_set(EnterSchedules::<SubState>::default()),
944
);
945
946
schedules.add_systems(
947
StateTransition,
948
register_transition("computed exit")
949
.in_set(ExitSchedules::<TransitionTestingComputedState>::default()),
950
);
951
schedules.add_systems(
952
StateTransition,
953
register_transition("computed transition")
954
.in_set(TransitionSchedules::<TransitionTestingComputedState>::default()),
955
);
956
schedules.add_systems(
957
StateTransition,
958
register_transition("computed enter")
959
.in_set(EnterSchedules::<TransitionTestingComputedState>::default()),
960
);
961
962
world.insert_resource(schedules);
963
964
world.run_schedule(StateTransition);
965
966
let transitions = &world.resource::<TransitionTracker>().0;
967
968
assert_eq!(transitions.len(), 9);
969
assert_eq!(transitions[0], "computed exit");
970
assert_eq!(transitions[1], "sub exit");
971
assert_eq!(transitions[2], "simple exit");
972
// Transition order is arbitrary and doesn't need testing.
973
assert_eq!(transitions[6], "simple enter");
974
assert_eq!(transitions[7], "sub enter");
975
assert_eq!(transitions[8], "computed enter");
976
}
977
}
978
979